diff --git a/src/dorkbox/swingActiveRender/ActiveRenderLoop.kt b/src/dorkbox/swingActiveRender/ActiveRenderLoop.kt index 5301700..c8e6ea0 100644 --- a/src/dorkbox/swingActiveRender/ActiveRenderLoop.kt +++ b/src/dorkbox/swingActiveRender/ActiveRenderLoop.kt @@ -15,9 +15,11 @@ */ package dorkbox.swingActiveRender -import dorkbox.propertyLoader.Property +import dorkbox.swingActiveRender.SwingActiveRender.TARGET_FPS +import java.awt.Component import java.awt.Graphics import java.awt.Toolkit +import java.util.concurrent.* import java.util.concurrent.locks.* import kotlin.concurrent.withLock @@ -33,22 +35,12 @@ class ActiveRenderLoop : Runnable { private val condition = lock.newCondition() private val runTimeCondition = lock.newCondition() - companion object { - /** - * How many frames per second we want the Swing ActiveRender thread to run at - * - * ### NOTE: - * - * The ActiveRenderLoop replaces the Swing EDT (only for specified JFrames) in order to enable smoother animations. It is also - * important to REMEMBER -- if you add a component to an actively managed JFrame, YOU MUST make sure to call - * [javax.swing.JComponent.setIgnoreRepaint] otherwise this component will "fight" on the EDT for updates. - * - * You can ALSO completely disable the Swing EDT by calling [NullRepaintManager.install] - */ - @Property(description = "How many frames per second the Swing ActiveRender thread will run at.") - @Volatile - var TARGET_FPS = 30 - } + + // volatile, so that access triggers thread synchrony + @Volatile + private var hasActiveRenders = false + private val activeRenders: MutableList = CopyOnWriteArrayList() + override fun run() { currentThread = Thread.currentThread() @@ -66,23 +58,23 @@ class ActiveRenderLoop : Runnable { val OPTIMAL_TIME = (1000000000 / TARGET_FPS).toLong() var graphics: Graphics? = null + // is a copy-on-write, and it must eventually be correct. + val renderEvents = SwingActiveRender.activeRenderEvents + while (true) { // if we have NO active renderers, just wait for one. - while (SwingActiveRender.hasActiveRenders) { + while (hasActiveRenders) { try { val now = System.nanoTime() val updateDeltaNanos = now - lastTime lastTime = now - // is a copy-on-write, and it must eventually be correct. - val renderEvents = SwingActiveRender.activeRenderEvents - renderEvents.forEach { - it.invoke(updateDeltaNanos) + for (event in renderEvents) { + event.invoke(updateDeltaNanos) } - // this needs to be synchronized because we don't want to our canvas removed WHILE we are rendering it. - val activeRenders = SwingActiveRender.activeRenders + val activeRenders = this.activeRenders for (component in activeRenders) { if (!component.isDisplayable) { continue @@ -111,7 +103,7 @@ class ActiveRenderLoop : Runnable { // see: http://www.cs.nuim.ie/~jpower/Research/Papers/2008/lambert-qapl08.pdf // Also, down-casting (long -> int) is not expensive w.r.t IDIV/LDIV val l = (lastTime - System.nanoTime() + OPTIMAL_TIME).toInt() - val millis = l / 1000000 + val millis = l / 1_000_000 if (millis > 1) { Thread.sleep(millis.toLong()) @@ -133,7 +125,18 @@ class ActiveRenderLoop : Runnable { } } - fun signalStart() { + fun maybeShutdown(component: Component): Boolean { + activeRenders.remove(component) + + val hasActiveRenders = activeRenders.isNotEmpty() + this.hasActiveRenders = hasActiveRenders + return hasActiveRenders + } + + fun signalStart(component: Component) { + hasActiveRenders = true + activeRenders.add(component) + lock.withLock { runTimeCondition.signal() } diff --git a/src/dorkbox/swingActiveRender/SwingActiveRender.kt b/src/dorkbox/swingActiveRender/SwingActiveRender.kt index 22c1fc5..e4dd3f7 100644 --- a/src/dorkbox/swingActiveRender/SwingActiveRender.kt +++ b/src/dorkbox/swingActiveRender/SwingActiveRender.kt @@ -15,10 +15,12 @@ */ package dorkbox.swingActiveRender +import dorkbox.propertyLoader.Property import dorkbox.updates.Updates.add +import java.awt.Canvas import java.awt.Component +import java.awt.Window import java.util.concurrent.* -import java.util.concurrent.atomic.* import javax.swing.SwingUtilities /** @@ -32,26 +34,41 @@ import javax.swing.SwingUtilities */ @Suppress("MemberVisibilityCanBePrivate") object SwingActiveRender { + /** + * How many frames per second we want the Swing ActiveRender thread to run at + * + * ### NOTE: + * + * The ActiveRenderLoop replaces the Swing EDT (only for specified JFrames) in order to enable smoother animations. It is also + * important to REMEMBER -- if you add a component to an actively managed JFrame, YOU MUST make sure to call + * [javax.swing.JComponent.setIgnoreRepaint] otherwise this component will "fight" on the EDT for updates. + * + * You can ALSO completely disable the Swing EDT by calling [NullRepaintManager.install] + */ + @Property(description = "How many frames per second the Swing Active Render thread will run at.") @Volatile - private var activeRenderThread: Thread? = null + var TARGET_FPS = 30 + + + private val activeRenderThread: Thread - internal val activeRenders: MutableList = CopyOnWriteArrayList() internal val activeRenderEvents: MutableList<(deltaInNanos: Long)->Unit> = CopyOnWriteArrayList() - // volatile, so that access triggers thread synchrony - @Volatile - internal var hasActiveRenders = false - private val renderLoop = ActiveRenderLoop() /** * Gets the version number. */ - const val version = "1.4" + const val version = "1.5" init { // Add this project to the updates system, which verifies this class + UUID + version information add(SwingActiveRender::class.java, "0dfec3d996f3420d82a864c6cd5a2646", version) + + // this will pause and wait for a signal. Since this class is used, this means that we should create the threads + activeRenderThread = Thread(renderLoop, "Swing-ActiveRender") + activeRenderThread.isDaemon = true + activeRenderThread.start() } /** @@ -65,22 +82,24 @@ object SwingActiveRender { * @param component the component to add to the ActiveRender thread. */ fun add(component: Component) { - SwingUtilities.invokeLater { + if (SwingUtilities.isEventDispatchThread()) { // this part has to be on the swing EDT component.ignoreRepaint = true - } - - - synchronized(renderLoop) { - if (!hasActiveRenders) { - hasActiveRenders = true - setupActiveRenderThread() + } else { + SwingUtilities.invokeAndWait { + // this part has to be on the swing EDT + component.ignoreRepaint = true } - - activeRenders.add(component) } - renderLoop.signalStart() + if (component is Window) { + component.createBufferStrategy(2) + } else if (component is Canvas) { + component.createBufferStrategy(2) + } + + setupActiveRenderThread() + renderLoop.signalStart(component) } /** @@ -89,22 +108,17 @@ object SwingActiveRender { * @param component the component to remove */ fun remove(component: Component) { - SwingUtilities.invokeLater { + if (SwingUtilities.isEventDispatchThread()) { // this part has to be on the swing EDT component.ignoreRepaint = false - } - - - synchronized(renderLoop) { - activeRenders.remove(component) - - val hasActiveRenders = activeRenders.isNotEmpty() - this.hasActiveRenders = hasActiveRenders - if (!hasActiveRenders) { - // our thread loop will kill itself - activeRenderThread = null + } else { + SwingUtilities.invokeAndWait { + // this part has to be on the swing EDT + component.ignoreRepaint = false } } + + renderLoop.maybeShutdown(component) } /** @@ -138,13 +152,7 @@ object SwingActiveRender { fun isDispatchThread(): Boolean { // make sure we are initialized! - if (activeRenderThread == null) { - synchronized(renderLoop) { - if (activeRenderThread == null) { - setupActiveRenderThread() - } - } - } + setupActiveRenderThread() return Thread.currentThread() == renderLoop.currentThread } @@ -153,14 +161,6 @@ object SwingActiveRender { * Creates (if necessary) the active-render thread. When there are no active-render targets, this thread will exit */ private fun setupActiveRenderThread() { - if (activeRenderThread != null) { - return - } - - activeRenderThread = Thread(renderLoop, "Swing-ActiveRender") - activeRenderThread!!.isDaemon = true - activeRenderThread!!.start() - renderLoop.waitForStartup() } }