diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java index 4ad9d1f544..4308395aa0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java @@ -53,8 +53,6 @@ public class ZoneView implements ModelChangeListener { private final Map>> brightLightCache = new Hashtable<>(); /** Map the PlayerView to its visible area. */ private final Map visibleAreaMap = new HashMap<>(); - /** Hold all of our lights combined by lumens. Used for hard FoW reveal. */ - private final SortedMap allLightAreaMap = new ConcurrentSkipListMap<>(); /** Map each token to their personal bright light source area. */ private final Map> personalBrightLightCache = new HashMap<>(); /** Map each token to their personal drawable lights. */ @@ -428,13 +426,14 @@ public Area getVisibleArea(Token token) { CombineLightsSwingWorker workerThread = new CombineLightsSwingWorker(token.getSightType(), lightSourceTokens); workerThread.execute(); + SortedMap allLightAreaMap; try { - workerThread - .get(); // Jamz: We need to wait for this thread (which spawns more threads) to finish - // before we go on + // Jamz: We need to wait for this thread (which spawns more threads) to finish before we go + // on + allLightAreaMap = workerThread.get(); } catch (InterruptedException | ExecutionException e) { - // TODO Auto-generated catch block e.printStackTrace(); + allLightAreaMap = Collections.emptySortedMap(); } // log.info("CombineLightsSwingWorker: \t" + stopwatch); @@ -477,11 +476,11 @@ public Area getVisibleArea(Token token) { } } } + allLightAreaMap.clear(); // Dispose of object, only needed for the scope of this method tokenVisibleArea = allLightArea; } - allLightAreaMap.clear(); // Dispose of object, only needed for the scope of this method tokenVisionCache.put(token.getId(), tokenVisibleArea); // log.info("getVisibleArea: \t\t" + stopwatch); @@ -489,7 +488,7 @@ public Area getVisibleArea(Token token) { return tokenVisibleArea; } - private class CombineLightsSwingWorker extends SwingWorker> { + private class CombineLightsSwingWorker extends SwingWorker, List> { private final String sightName; private final List lightSourceTokens; private final ExecutorService lightsThreadPool; @@ -502,16 +501,26 @@ private CombineLightsSwingWorker(String sightName, List lightSourceTokens } @Override - protected Void doInBackground() throws Exception { + protected SortedMap doInBackground() throws Exception { + /* Hold all of our lights combined by lumens. Used for hard FoW reveal. */ + var allLightPathMap = new ConcurrentSkipListMap(); + for (Token lightSourceToken : lightSourceTokens) { - CombineLightsTask task = new CombineLightsTask(sightName, lightSourceToken); + CombineLightsTask task = + new CombineLightsTask(allLightPathMap, sightName, lightSourceToken); lightsThreadPool.submit(task); } lightsThreadPool.shutdown(); lightsThreadPool.awaitTermination(3, TimeUnit.MINUTES); - return null; + // Convert the luminous paths to areas. + var allLightAreaMap = new TreeMap(); + for (var entry : allLightPathMap.entrySet()) { + allLightAreaMap.put(entry.getKey(), new Area(entry.getValue())); + } + + return allLightAreaMap; } @Override @@ -530,10 +539,13 @@ public void done() { * task */ private final class CombineLightsTask implements Callable> { + private final Map allLightPathMap; private final String sightName; private final Token lightSourceToken; - private CombineLightsTask(String sightName, Token lightSourceToken) { + private CombineLightsTask( + Map allLightPathMap, String sightName, Token lightSourceToken) { + this.allLightPathMap = allLightPathMap; this.sightName = sightName; this.lightSourceToken = lightSourceToken; } @@ -543,22 +555,21 @@ public Map call() { Map lightArea = getLightSourceArea(sightName, lightSourceToken); for (Entry light : lightArea.entrySet()) { - // Area tempArea = light.getValue(); - Path2D path = new Path2D.Double(); - path.append(light.getValue().getPathIterator(null, 1), false); - - synchronized (allLightAreaMap) { - if (allLightAreaMap.containsKey(light.getKey())) { - // Area allLight = allLightAreaMap.get(light.getKey()); - // tempArea.add(allLight); - - // Path2D is faster than Area it looks like - path.append(allLightAreaMap.get(light.getKey()).getPathIterator(null, 1), false); - } - - // allLightAreaMap.put(light.getKey(), tempArea); - allLightAreaMap.put(light.getKey(), new Area(path)); - } + // Add the token's light area to the global area in `allLightPathMap`. + // Because the remapping function may be called multiple times in case a retry is required, + // we make sure to use a clean `Path2D` object for each invocation. We also need to make + // sure we don't modify `existingValue` as such a change may result in an inconsistent + // state. + allLightPathMap.compute( + light.getKey(), + (lumens, totalPath) -> { + Path2D path = new Path2D.Double(); + path.append(light.getValue().getPathIterator(null, 1), false); + if (totalPath != null) { + path.append(totalPath.getPathIterator(null, 1), false); + } + return path; + }); } return lightArea;