runelite-client: Make OverlayManager thread safe

There was something happening where rebuildOverlayLayers would race itself almost 100% of the time, preventing overlays from being removed.
This commit is contained in:
Max Weber
2018-06-12 20:49:29 -06:00
committed by Abex
parent e94beb922b
commit e0d70ab382
3 changed files with 89 additions and 80 deletions

View File

@@ -27,13 +27,18 @@ package net.runelite.client.ui.overlay;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Point; import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.TreeSet;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
@@ -50,11 +55,34 @@ public class OverlayManager
private static final String OVERLAY_CONFIG_PREFERRED_SIZE = "_preferredSize"; private static final String OVERLAY_CONFIG_PREFERRED_SIZE = "_preferredSize";
private static final String RUNELITE_CONFIG_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).keyName(); private static final String RUNELITE_CONFIG_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).keyName();
@Getter @VisibleForTesting
private final List<Overlay> overlays = new CopyOnWriteArrayList<>(); static final Comparator<Overlay> OVERLAY_COMPARATOR = (a, b) ->
{
if (a.getPosition() != b.getPosition())
{
// This is so non-dynamic overlays render after dynamic
// overlays, which are generally in the scene
return a.getPosition().compareTo(b.getPosition());
}
@Getter // For dynamic overlays, higher priority means to
private final Map<OverlayLayer, List<Overlay>> overlayLayers = new ConcurrentHashMap<>(); // draw *later* so it is on top.
// For non-dynamic overlays, higher priority means
// draw *first* so that they are closer to their
// defined position.
return a.getPosition() == OverlayPosition.DYNAMIC
? a.getPriority().compareTo(b.getPriority())
: b.getPriority().compareTo(a.getPriority());
};
/**
* Sorted set of overlays
* All access to this must be guarded by a lock on this OverlayManager
*/
@Getter(AccessLevel.PACKAGE)
private final Set<Overlay> overlays = new TreeSet<>(OVERLAY_COMPARATOR);
private final Map<OverlayLayer, List<Overlay>> overlayLayers = new HashMap<>();
private final ConfigManager configManager; private final ConfigManager configManager;
@@ -64,13 +92,24 @@ public class OverlayManager
this.configManager = configManager; this.configManager = configManager;
} }
/**
* Gets all of the overlays on a layer
*
* @param layer the layer
* @return An immutable list of all of the overlays on that layer
*/
synchronized List<Overlay> getLayer(OverlayLayer layer)
{
return overlayLayers.get(layer);
}
/** /**
* Add overlay. * Add overlay.
* *
* @param overlay the overlay * @param overlay the overlay
* @return true if overlay was added * @return true if overlay was added
*/ */
public boolean add(final Overlay overlay) public synchronized boolean add(final Overlay overlay)
{ {
final boolean add = overlays.add(overlay); final boolean add = overlays.add(overlay);
@@ -82,7 +121,6 @@ public class OverlayManager
overlay.setPreferredSize(size); overlay.setPreferredSize(size);
final OverlayPosition position = loadOverlayPosition(overlay); final OverlayPosition position = loadOverlayPosition(overlay);
overlay.setPreferredPosition(position); overlay.setPreferredPosition(position);
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
} }
@@ -95,13 +133,12 @@ public class OverlayManager
* @param overlay the overlay * @param overlay the overlay
* @return true if overlay was removed * @return true if overlay was removed
*/ */
public boolean remove(final Overlay overlay) public synchronized boolean remove(final Overlay overlay)
{ {
final boolean remove = overlays.remove(overlay); final boolean remove = overlays.remove(overlay);
if (remove) if (remove)
{ {
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
} }
@@ -114,10 +151,9 @@ public class OverlayManager
* @param filter the filter * @param filter the filter
* @return true if any overlay was removed * @return true if any overlay was removed
*/ */
public boolean removeIf(Predicate<Overlay> filter) public synchronized boolean removeIf(Predicate<Overlay> filter)
{ {
final boolean removeIf = overlays.removeIf(filter); final boolean removeIf = overlays.removeIf(filter);
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
return removeIf; return removeIf;
} }
@@ -125,10 +161,9 @@ public class OverlayManager
/** /**
* Clear all overlays * Clear all overlays
*/ */
public void clear() public synchronized void clear()
{ {
overlays.clear(); overlays.clear();
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
} }
@@ -137,12 +172,11 @@ public class OverlayManager
* *
* @param overlay overlay to save * @param overlay overlay to save
*/ */
public void saveOverlay(final Overlay overlay) public synchronized void saveOverlay(final Overlay overlay)
{ {
saveOverlayPosition(overlay); saveOverlayPosition(overlay);
saveOverlaySize(overlay); saveOverlaySize(overlay);
saveOverlayLocation(overlay); saveOverlayLocation(overlay);
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
} }
@@ -151,7 +185,7 @@ public class OverlayManager
* *
* @param overlay overlay to reset * @param overlay overlay to reset
*/ */
public void resetOverlay(final Overlay overlay) public synchronized void resetOverlay(final Overlay overlay)
{ {
final String locationKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_LOCATION; final String locationKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_LOCATION;
final String positionKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_POSITION; final String positionKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_POSITION;
@@ -159,13 +193,15 @@ public class OverlayManager
configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, locationKey); configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, locationKey);
configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, positionKey); configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, positionKey);
configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, sizeKey); configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, sizeKey);
sortOverlays(overlays);
rebuildOverlayLayers(); rebuildOverlayLayers();
} }
private void rebuildOverlayLayers() private synchronized void rebuildOverlayLayers()
{ {
overlayLayers.clear(); for (OverlayLayer l : OverlayLayer.values())
{
overlayLayers.put(l, new ArrayList<>());
}
for (final Overlay overlay : overlays) for (final Overlay overlay : overlays)
{ {
@@ -181,17 +217,10 @@ public class OverlayManager
} }
} }
overlayLayers.compute(layer, (key, value) -> overlayLayers.get(layer).add(overlay);
{
if (value == null)
{
value = new CopyOnWriteArrayList<>();
}
value.add(overlay);
return value;
});
} }
overlayLayers.forEach((layer, value) -> overlayLayers.put(layer, Collections.unmodifiableList(value)));
} }
private void saveOverlayLocation(final Overlay overlay) private void saveOverlayLocation(final Overlay overlay)
@@ -265,27 +294,4 @@ public class OverlayManager
final String locationKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_POSITION; final String locationKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_POSITION;
return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, locationKey, OverlayPosition.class); return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, locationKey, OverlayPosition.class);
} }
@VisibleForTesting
static void sortOverlays(List<Overlay> overlays)
{
overlays.sort((a, b) ->
{
if (a.getPosition() != b.getPosition())
{
// This is so non-dynamic overlays render after dynamic
// overlays, which are generally in the scene
return a.getPosition().compareTo(b.getPosition());
}
// For dynamic overlays, higher priority means to
// draw *later* so it is on top.
// For non-dynamic overlays, higher priority means
// draw *first* so that they are closer to their
// defined position.
return a.getPosition() == OverlayPosition.DYNAMIC
? a.getPriority().compareTo(b.getPriority())
: b.getPriority().compareTo(a.getPriority());
});
}
} }

View File

@@ -123,7 +123,7 @@ public class OverlayRenderer extends MouseListener implements KeyListener
public void render(Graphics2D graphics, final OverlayLayer layer) public void render(Graphics2D graphics, final OverlayLayer layer)
{ {
final Client client = clientProvider.get(); final Client client = clientProvider.get();
final List<Overlay> overlays = overlayManager.getOverlayLayers().get(layer); final List<Overlay> overlays = overlayManager.getLayer(layer);
if (client == null if (client == null
|| overlays == null || overlays == null
@@ -245,36 +245,39 @@ public class OverlayRenderer extends MouseListener implements KeyListener
final Point mousePoint = mouseEvent.getPoint(); final Point mousePoint = mouseEvent.getPoint();
mousePosition.setLocation(mousePoint); mousePosition.setLocation(mousePoint);
for (Overlay overlay : overlayManager.getOverlays()) synchronized (overlayManager)
{ {
if (overlay.getBounds().contains(mousePoint)) for (Overlay overlay : overlayManager.getOverlays())
{ {
if (SwingUtilities.isRightMouseButton(mouseEvent)) if (overlay.getBounds().contains(mousePoint))
{ {
// detached overlays have no place to reset back to if (SwingUtilities.isRightMouseButton(mouseEvent))
if (overlay.getPosition() != OverlayPosition.DETACHED)
{ {
overlay.setPreferredPosition(null); // detached overlays have no place to reset back to
overlay.setPreferredSize(null); if (overlay.getPosition() != OverlayPosition.DETACHED)
overlay.setPreferredLocation(null); {
overlayManager.resetOverlay(overlay); overlay.setPreferredPosition(null);
overlay.setPreferredSize(null);
overlay.setPreferredLocation(null);
overlayManager.resetOverlay(overlay);
}
} }
} else
else {
{ final Point offset = new Point(mousePoint.x, mousePoint.y);
final Point offset = new Point(mousePoint.x, mousePoint.y); offset.translate(-overlay.getBounds().x, -overlay.getBounds().y);
offset.translate(-overlay.getBounds().x, -overlay.getBounds().y); overlayOffset.setLocation(offset);
overlayOffset.setLocation(offset);
mousePoint.translate(-offset.x, -offset.y); mousePoint.translate(-offset.x, -offset.y);
movedOverlay = overlay; movedOverlay = overlay;
movedOverlay.setPreferredPosition(null); movedOverlay.setPreferredPosition(null);
movedOverlay.setPreferredLocation(mousePoint); movedOverlay.setPreferredLocation(mousePoint);
overlayManager.saveOverlay(movedOverlay); overlayManager.saveOverlay(movedOverlay);
} }
mouseEvent.consume(); mouseEvent.consume();
break; break;
}
} }
} }

View File

@@ -55,7 +55,7 @@ public class OverlayManagerTest
Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH); Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH);
Overlay tll = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.LOW); Overlay tll = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.LOW);
List<Overlay> overlays = Arrays.asList(tlh, tll); List<Overlay> overlays = Arrays.asList(tlh, tll);
OverlayManager.sortOverlays(overlays); overlays.sort(OverlayManager.OVERLAY_COMPARATOR);
assertEquals(tlh, overlays.get(0)); assertEquals(tlh, overlays.get(0));
assertEquals(tll, overlays.get(1)); assertEquals(tll, overlays.get(1));
} }
@@ -67,7 +67,7 @@ public class OverlayManagerTest
Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH); Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH);
Overlay dyn = new TestOverlay(OverlayPosition.DYNAMIC, OverlayPriority.HIGH); Overlay dyn = new TestOverlay(OverlayPosition.DYNAMIC, OverlayPriority.HIGH);
List<Overlay> overlays = Arrays.asList(tlh, dyn); List<Overlay> overlays = Arrays.asList(tlh, dyn);
OverlayManager.sortOverlays(overlays); overlays.sort(OverlayManager.OVERLAY_COMPARATOR);
assertEquals(dyn, overlays.get(0)); assertEquals(dyn, overlays.get(0));
assertEquals(tlh, overlays.get(1)); assertEquals(tlh, overlays.get(1));
} }
@@ -80,7 +80,7 @@ public class OverlayManagerTest
Overlay dyn = new TestOverlay(OverlayPosition.DYNAMIC, OverlayPriority.HIGH); Overlay dyn = new TestOverlay(OverlayPosition.DYNAMIC, OverlayPriority.HIGH);
Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH); Overlay tlh = new TestOverlay(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH);
List<Overlay> overlays = Arrays.asList(t, dyn, tlh); List<Overlay> overlays = Arrays.asList(t, dyn, tlh);
OverlayManager.sortOverlays(overlays); overlays.sort(OverlayManager.OVERLAY_COMPARATOR);
assertEquals(dyn, overlays.get(0)); assertEquals(dyn, overlays.get(0));
assertEquals(tlh, overlays.get(1)); assertEquals(tlh, overlays.get(1));
assertEquals(t, overlays.get(2)); assertEquals(t, overlays.get(2));