Skip to content

Commit

Permalink
Merge pull request #3745 from kwvanderlinde/refactor/3744-cache-expos…
Browse files Browse the repository at this point in the history
…ed-area

Cache the exposed area used for pathfinding and rendering FoW
  • Loading branch information
Phergus authored Nov 23, 2022
2 parents 1c83666 + feb700e commit 305d92a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 171 deletions.
181 changes: 26 additions & 155 deletions src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,6 @@ public class ZoneRenderer extends JComponent
private Zone.Layer activeLayer;
private String loadingProgress;
private boolean isLoaded;
private BufferedImage fogBuffer;
/**
* I don't like this, at all, but it'll work for now, basically keep track of when the fog cache
* needs to be flushed in the case of switching views
*/
private boolean flushFog = true;

/** In screen space */
private Area exposedFogArea;
Expand Down Expand Up @@ -309,7 +303,6 @@ public void setZoneScale(Scale scale) {
evt -> {
if (Scale.PROPERTY_SCALE.equals(evt.getPropertyName())) {
tokenLocationCache.clear();
flushFog = true;
}
if (Scale.PROPERTY_OFFSET.equals(evt.getPropertyName())) {
// flushFog = true;
Expand Down Expand Up @@ -654,7 +647,7 @@ public void centerOn(CellPoint point) {
/**
* Remove the token from: tokenLocationCache, flipImageMap, opacityImageMap, replacementImageMap,
* labelRenderingCache. Set the visibleScreenArea, tokenStackMap, drawableLights, drawableAuras to
* null. Flush the fog. Flush the token from the zoneView.
* null. Flush the token from the zoneView.
*
* @param token the token to flush
*/
Expand All @@ -674,7 +667,6 @@ public void flush(Token token) {
// This could also be smarter
tokenStackMap = null;

flushFog = true;
drawableLights = null;
drawableAuras = null;

Expand All @@ -700,9 +692,9 @@ public void flush() {
flushDrawableRenderer();
flipImageMap.clear();
flipIsoImageMap.clear();
fogBuffer = null;
drawableLights = null;
drawableAuras = null;
zoneView.flushFog();

isLoaded = false;
}
Expand All @@ -717,7 +709,6 @@ public void flushLight() {

/** Set flushFog to true, visibleScreenArea to null, and repaints */
public void flushFog() {
flushFog = true;
visibleScreenArea = null;
repaintDebouncer.dispatch();
}
Expand Down Expand Up @@ -1076,11 +1067,9 @@ public Rectangle zoneExtents(PlayerView view) {

/**
* This method clears {@link #drawableAuras}, {@link #drawableLights}, {@link #visibleScreenArea},
* and {@link #lastView}. It also flushes the {@link #zoneView} and sets the {@link #flushFog}
* flag so that fog will be recalculated.
* and {@link #lastView}. It also flushes the {@link #zoneView}.
*/
public void invalidateCurrentViewCache() {
flushFog = true;
drawableLights = null;
drawableAuras = null;
visibleScreenArea = null;
Expand Down Expand Up @@ -1616,9 +1605,8 @@ private void renderPlayerVisionOverlay(Graphics2D g, PlayerView view) {
Area clip = new Area(new Rectangle(getSize().width, getSize().height));

Area viewArea = new Area(exposedFogArea);
List<Token> tokens = view.getTokens();
if (tokens != null && !tokens.isEmpty()) {
for (Token tok : tokens) {
if (view.isUsingTokenView()) {
for (Token tok : view.getTokens()) {
ExposedAreaMetaData exposedMeta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID());
viewArea.add(exposedMeta.getExposedAreaHistory());
}
Expand Down Expand Up @@ -1745,79 +1733,23 @@ private void renderLabels(Graphics2D g, PlayerView view) {
timer.stop("labels-1");
}

// Private cache variables just for renderFog() and no one else. :)
Integer fogX = null;
Integer fogY = null;

private Area renderFog(Graphics2D g, PlayerView view) {
private void renderFog(Graphics2D g, PlayerView view) {
Dimension size = getSize();
Area fogClip = new Area(new Rectangle(0, 0, size.width, size.height));
Area combined = null;

// Optimization for panning
if (!flushFog
&& fogX != null
&& fogY != null
&& (fogX != getViewOffsetX() || fogY != getViewOffsetY())) {
// This optimization does not seem to keep the alpha channel correctly, and sometimes leaves
// lines on some graphics boards, we'll leave it out for now
// if (Math.abs(fogX - getViewOffsetX()) < size.width && Math.abs(fogY - getViewOffsetY()) <
// size.height) {
// int deltaX = getViewOffsetX() - fogX;
// int deltaY = getViewOffsetY() - fogY;
//
// Graphics2D buffG = fogBuffer.createGraphics();
//
// buffG.setComposite(AlphaComposite.Src);
// buffG.copyArea(0, 0, size.width, size.height, deltaX, deltaY);
// buffG.dispose();
//
// fogClip = new Area();
// if (deltaX < 0) {
// fogClip.add(new Area(new Rectangle(size.width+deltaX, 0, -deltaX, size.height)));
// } else if (deltaX > 0){
// fogClip.add(new Area(new Rectangle(0, 0, deltaX, size.height)));
// }
//
// if (deltaY < 0) {
// fogClip.add(new Area(new Rectangle(0, size.height + deltaY, size.width, -deltaY)));
// } else if (deltaY > 0) {
// fogClip.add(new Area(new Rectangle(0, 0, size.width, deltaY)));
// }
// }
flushFog = true;
}
boolean cacheNotValid =
(fogBuffer == null
|| fogBuffer.getWidth() != size.width
|| fogBuffer.getHeight() != size.height);
timer.start("renderFog");
if (flushFog || cacheNotValid) {
fogX = getViewOffsetX();
fogY = getViewOffsetY();

boolean newImage = false;
if (cacheNotValid) {
newImage = true;
timer.start("renderFog-allocateBufferedImage");
fogBuffer = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB_PRE);
timer.stop("renderFog-allocateBufferedImage");
}
Graphics2D buffG = fogBuffer.createGraphics();

timer.start("renderFog-allocateBufferedImage");
try (final var entry = tempBufferPool.acquire()) {
final var buffer = entry.get();
timer.stop("renderFog-allocateBufferedImage");

timer.start("renderFog");
final var fogX = getViewOffsetX();
final var fogY = getViewOffsetY();

Graphics2D buffG = buffer.createGraphics();
buffG.setClip(fogClip);
SwingUtil.useAntiAliasing(buffG);

// XXX Is this even needed? Immediately below is another call to fillRect() with the same
// dimensions!
if (!newImage) {
timer.start("renderFog-clearOldImage");
// Composite oldComposite = buffG.getComposite();
buffG.setComposite(AlphaComposite.Clear);
// buffG.fillRect(0, 0, size.width, size.height); // Jamz: Removed as it's called again
// below
// buffG.setComposite(oldComposite);
timer.stop("renderFog-clearOldImage");
}
timer.start("renderFog-fill");
// Fill
double scale = getScale();
Expand Down Expand Up @@ -1848,84 +1780,23 @@ private Area renderFog(Graphics2D g, PlayerView view) {

String msg = null;
if (timer.isEnabled()) {
List<Token> list = view.getTokens();
msg = "renderFog-combined(" + (list == null ? 0 : list.size()) + ")";
msg = "renderFog-combined(" + (view.isUsingTokenView() ? view.getTokens().size() : 0) + ")";
}
timer.start(msg);
combined = zone.getExposedArea(view);
Area combined = zoneView.getExposedArea(view);
timer.stop(msg);

timer.start("renderFogArea");
Area exposedArea = null;
Area tempArea = new Area();
boolean combinedView =
!zoneView.isUsingVision()
|| MapTool.isPersonalServer()
|| !MapTool.getServerPolicy().isUseIndividualFOW()
|| view.isGMView();

if (view.getTokens() != null) {
// if there are tokens selected combine the areas, then, if individual FOW is enabled
// we pass the combined exposed area to build the soft FOW and visible area.
for (Token tok : view.getTokens()) {
ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID());
exposedArea = meta.getExposedAreaHistory();
tempArea.add(new Area(exposedArea));
}
if (combinedView) {
// combined = zone.getExposedArea(view);
buffG.fill(combined);
renderFogArea(buffG, view, combined, visibleArea);
renderFogOutline(buffG, view, visibleArea);
} else {
// 'combined' already includes the area encompassed by 'tempArea', so just
// use 'combined' instead in this block of code?
tempArea.add(combined);
buffG.fill(tempArea);
renderFogArea(buffG, view, tempArea, visibleArea);
renderFogOutline(buffG, view, visibleArea);
}
} else {
// No tokens selected, so if we are using Individual FOW, we build up all the owned tokens
// exposed area's to build the soft FOW.
if (combinedView) {
if (combined.isEmpty()) {
combined = zone.getExposedArea();
}
buffG.fill(combined);
renderFogArea(buffG, view, combined, visibleArea);
renderFogOutline(buffG, view, visibleArea);
} else {
Area myCombined = new Area();
List<Token> myToks = zone.getTokens();
for (Token tok : myToks) {
if (!AppUtil.playerOwns(
tok)) { // Only here if !isGMview() so should the tokens already be in
// PlayerView.getTokens()?
continue;
}
ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID());
exposedArea = meta.getExposedAreaHistory();
myCombined.add(new Area(exposedArea));
}
buffG.fill(myCombined);
renderFogArea(buffG, view, myCombined, visibleArea);
renderFogOutline(buffG, view, visibleArea);
}
}
// renderFogArea(buffG, view, combined, visibleArea);
buffG.fill(combined);
renderFogArea(buffG, view, combined, visibleArea);
renderFogOutline(buffG, view, visibleArea);
timer.stop("renderFogArea");

// timer.start("renderFogOutline");
// renderFogOutline(buffG, view, combined);
// timer.stop("renderFogOutline");

buffG.dispose();
flushFog = false;
timer.stop("renderFog");

g.drawImage(buffer, 0, 0, this);
}
timer.stop("renderFog");
g.drawImage(fogBuffer, 0, 0, this);
return combined;
}

private void renderFogArea(
Expand Down Expand Up @@ -4822,7 +4693,7 @@ public void modelChanged(ModelChangeEvent event) {
}
}
if (evt == Zone.Event.FOG_CHANGED) {
flushFog = true;
zoneView.flushFog();
}
if (evt == Zone.Event.DRAWABLE_ADDED || evt == Zone.Event.DRAWABLE_REMOVED) {
DrawnElement de = (DrawnElement) event.getArg();
Expand Down
50 changes: 48 additions & 2 deletions src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public class ZoneView implements ModelChangeListener {
private final Map<LightSource.Type, Set<GUID>> lightSourceMap = new HashMap<>();
/** Map each token to their map between sightType and set of lights. */
private final Map<GUID, Map<String, Set<DrawableLight>>> drawableLightCache = new HashMap<>();
/** Map the PlayerView to its exposed area. */
private final Map<PlayerView, Area> exposedAreaMap = new HashMap<>();
/** Map the PlayerView to its visible area. */
private final Map<PlayerView, VisibleAreaMeta> visibleAreaMap = new HashMap<>();
/** Map each token to their personal drawable lights. */
Expand All @@ -70,6 +72,39 @@ public ZoneView(Zone zone) {
zone.addModelChangeListener(this);
}

public Area getExposedArea(PlayerView view) {
Area exposed = exposedAreaMap.get(view);

if (exposed == null) {
boolean combinedView =
!isUsingVision()
|| MapTool.isPersonalServer()
|| !MapTool.getServerPolicy().isUseIndividualFOW()
|| view.isGMView();

if (view.isUsingTokenView() || combinedView) {
exposed = zone.getExposedArea(view);
} else {
// Not a token-specific view, but we are using Individual FoW. So we build up all the owned
// tokens' exposed areas to build the soft FoW. Note that not all owned tokens may still
// have sight (so weren't included in the PlayerView), but could still have previously
// exposed areas.
exposed = new Area();
for (Token tok : zone.getTokens()) {
if (!AppUtil.playerOwns(tok)) {
continue;
}
ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID());
Area exposedArea = meta.getExposedAreaHistory();
exposed.add(new Area(exposedArea));
}
}

exposedAreaMap.put(view, exposed);
}
return exposed;
}

/**
* Calculate the visible area of the view, cache it in visibleAreaMap, and return it
*
Expand Down Expand Up @@ -588,7 +623,7 @@ public Set<DrawableLight> getDrawableLights(PlayerView view) {
lightSet.addAll(set);
}
}
if (view != null && view.getTokens() != null) {
if (view != null && view.isUsingTokenView()) {
// Get the personal drawable lights of the tokens of the player view
for (Token token : view.getTokens()) {
Set<DrawableLight> lights = personalDrawableLightCache.get(token.getId());
Expand All @@ -608,11 +643,16 @@ public void flush() {
tokenVisibleAreaCache.clear();
tokenVisionCache.clear();
lightSourceCache.clear();
exposedAreaMap.clear();
visibleAreaMap.clear();
drawableLightCache.clear();
personalDrawableLightCache.clear();
}

public void flushFog() {
exposedAreaMap.clear();
}

/**
* Flush the ZoneView cache of the token. Remove token from tokenVisibleAreaCache,
* tokenVisionCache, lightSourceCache, drawableLightCache, and personal light caches. Can clear
Expand All @@ -632,8 +672,10 @@ public void flush(Token token) {
if (hadLightSource || token.hasLightSources()) {
// Have to recalculate all token vision
tokenVisionCache.clear();
exposedAreaMap.clear();
visibleAreaMap.clear();
} else if (token.getHasSight()) {
exposedAreaMap.clear();
visibleAreaMap.clear();
}
}
Expand Down Expand Up @@ -739,6 +781,7 @@ public void modelChanged(ModelChangeEvent event) {
lightSourceCache.clear();
drawableLightCache.clear();
personalDrawableLightCache.clear();
exposedAreaMap.clear();
visibleAreaMap.clear();
topologyAreas.clear();
topologyTrees.clear();
Expand Down Expand Up @@ -781,7 +824,10 @@ private boolean processTokenAddChangeEvent(List<Token> tokens) {
hasSight |= token.getHasSight();
}

if (hasSight) visibleAreaMap.clear();
if (hasSight) {
exposedAreaMap.clear();
visibleAreaMap.clear();
}

return hasTopology;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ protected List<CellPoint> calculatePath(CellPoint start, CellPoint goal) {

newFowExposedArea =
zoneRenderer.getZone().hasFog()
? zoneRenderer.getZone().getExposedArea(zoneRenderer.getPlayerView())
? zoneView.getExposedArea(zoneRenderer.getPlayerView())
: null;
}

Expand Down
Loading

0 comments on commit 305d92a

Please sign in to comment.