Quickly changing the facing of a token can create a java.util.ConcurrentModificationException
Describe the bug Quickly changing the facing of a token can create a java.util.ConcurrentModificationException:
java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1498)
at java.base/java.util.HashMap$ValueIterator.next(HashMap.java:1526)
at net.rptools.maptool.client.ui.zone.ZoneView.getDrawableLights(ZoneView.java:628)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.renderLights(ZoneRenderer.java:1462)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.renderZone(ZoneRenderer.java:1230)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.paintComponent(ZoneRenderer.java:872)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1633)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1608)
at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1546)
at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1313)
at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203)
at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:857)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:840)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:840)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:815)
at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:764)
at java.desktop/javax.swing.RepaintManager.access$1200(RepaintManager.java:69)
at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1880)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
at java.desktop/java.awt.EventQueue.access$600(EventQueue.java:97)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
at net.rptools.maptool.client.swing.MapToolEventQueue.dispatchEvent(MapToolEventQueue.java:43)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
or, the error
java.lang.NullPointerException
at net.rptools.maptool.client.ui.zone.ZoneView.calculateVisibleArea(ZoneView.java:598)
at net.rptools.maptool.client.ui.zone.ZoneView.getVisibleArea(ZoneView.java:92)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.renderFog(ZoneRenderer.java:1793)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.renderZone(ZoneRenderer.java:1285)
at net.rptools.maptool.client.ui.zone.ZoneRenderer.paintComponent(ZoneRenderer.java:837)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1633)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1608)
at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1546)
at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1313)
at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203)
at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:857)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:840)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:840)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:815)
at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:764)
at java.desktop/javax.swing.RepaintManager.access$1200(RepaintManager.java:69)
at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1880)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
at java.desktop/java.awt.EventQueue.access$600(EventQueue.java:97)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
at net.rptools.maptool.client.swing.MapToolEventQueue.dispatchEvent(MapToolEventQueue.java:43)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
To Reproduce I'm having trouble replicating the issue, but it seems changing the facing of a token with a cone lightsource quickly generates the exception.
MapTool Info
- Version: 1.5.7
- Install: New
Desktop (please complete the following information):
- OS: Windows
- Version: 10
Due to the rendering being done on a different thread now...? Might be enough to make a shallow copy in getDrawableLights() (or perhaps in renderLights()) of the data structure in question.
It would be more difficult — although likely a better solution — for a quick change of token facing to cancel the current render since a new one will be required anyway. I see two main issues with that, though: how to tell the separate thread to cancel the current render (there would need to be predefined points along the code path where it checks for cancelation and returns), and the fact that exposing FOW is done primarily in the rendering loop instead of outside of it. So a quick spin of token facing should expose 360° around the token, but if a render is canceled, it won't...
Or maybe there's a really simple solution and it has nothing to do with the rendering loop... 😉
@Merudo Have you seen anything like this in recent versions? Lighting looks quite different internally now, and I remember squashing a bunch of concurrency-related issues as well. I didn't know about this bug report at the time, otherwise I would have been more deliberate in making sure it was fixed.
And just a note for future me or anyone debugging this: the provided stacktrace appears to be from 1.5.8 or later, not 1.5.7.
I'm fairly certain that Merudo is (unfortunately) Foundry only these days.
I did some testing on this in 1.17 and I can't duplicate this (windows 11). I do not believe this is a valid bug today