Added support for window/canvas page flipping. Code cleanup/org.
This commit is contained in:
parent
0723fc432c
commit
6851b1483b
|
@ -15,9 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.swingActiveRender
|
package dorkbox.swingActiveRender
|
||||||
|
|
||||||
import dorkbox.propertyLoader.Property
|
import dorkbox.swingActiveRender.SwingActiveRender.TARGET_FPS
|
||||||
|
import java.awt.Component
|
||||||
import java.awt.Graphics
|
import java.awt.Graphics
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.locks.*
|
import java.util.concurrent.locks.*
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
|
@ -33,22 +35,12 @@ class ActiveRenderLoop : Runnable {
|
||||||
private val condition = lock.newCondition()
|
private val condition = lock.newCondition()
|
||||||
private val runTimeCondition = lock.newCondition()
|
private val runTimeCondition = lock.newCondition()
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
// volatile, so that access triggers thread synchrony
|
||||||
* How many frames per second we want the Swing ActiveRender thread to run at
|
@Volatile
|
||||||
*
|
private var hasActiveRenders = false
|
||||||
* ### NOTE:
|
private val activeRenders: MutableList<Component> = CopyOnWriteArrayList()
|
||||||
*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
currentThread = Thread.currentThread()
|
currentThread = Thread.currentThread()
|
||||||
|
@ -66,23 +58,23 @@ class ActiveRenderLoop : Runnable {
|
||||||
val OPTIMAL_TIME = (1000000000 / TARGET_FPS).toLong()
|
val OPTIMAL_TIME = (1000000000 / TARGET_FPS).toLong()
|
||||||
var graphics: Graphics? = null
|
var graphics: Graphics? = null
|
||||||
|
|
||||||
|
// is a copy-on-write, and it must eventually be correct.
|
||||||
|
val renderEvents = SwingActiveRender.activeRenderEvents
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// if we have NO active renderers, just wait for one.
|
// if we have NO active renderers, just wait for one.
|
||||||
|
|
||||||
while (SwingActiveRender.hasActiveRenders) {
|
while (hasActiveRenders) {
|
||||||
try {
|
try {
|
||||||
val now = System.nanoTime()
|
val now = System.nanoTime()
|
||||||
val updateDeltaNanos = now - lastTime
|
val updateDeltaNanos = now - lastTime
|
||||||
lastTime = now
|
lastTime = now
|
||||||
|
|
||||||
// is a copy-on-write, and it must eventually be correct.
|
for (event in renderEvents) {
|
||||||
val renderEvents = SwingActiveRender.activeRenderEvents
|
event.invoke(updateDeltaNanos)
|
||||||
renderEvents.forEach {
|
|
||||||
it.invoke(updateDeltaNanos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this needs to be synchronized because we don't want to our canvas removed WHILE we are rendering it.
|
val activeRenders = this.activeRenders
|
||||||
val activeRenders = SwingActiveRender.activeRenders
|
|
||||||
for (component in activeRenders) {
|
for (component in activeRenders) {
|
||||||
if (!component.isDisplayable) {
|
if (!component.isDisplayable) {
|
||||||
continue
|
continue
|
||||||
|
@ -111,7 +103,7 @@ class ActiveRenderLoop : Runnable {
|
||||||
// see: http://www.cs.nuim.ie/~jpower/Research/Papers/2008/lambert-qapl08.pdf
|
// 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
|
// Also, down-casting (long -> int) is not expensive w.r.t IDIV/LDIV
|
||||||
val l = (lastTime - System.nanoTime() + OPTIMAL_TIME).toInt()
|
val l = (lastTime - System.nanoTime() + OPTIMAL_TIME).toInt()
|
||||||
val millis = l / 1000000
|
val millis = l / 1_000_000
|
||||||
|
|
||||||
if (millis > 1) {
|
if (millis > 1) {
|
||||||
Thread.sleep(millis.toLong())
|
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 {
|
lock.withLock {
|
||||||
runTimeCondition.signal()
|
runTimeCondition.signal()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.swingActiveRender
|
package dorkbox.swingActiveRender
|
||||||
|
|
||||||
|
import dorkbox.propertyLoader.Property
|
||||||
import dorkbox.updates.Updates.add
|
import dorkbox.updates.Updates.add
|
||||||
|
import java.awt.Canvas
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
import java.awt.Window
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.atomic.*
|
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,26 +34,41 @@ import javax.swing.SwingUtilities
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
object SwingActiveRender {
|
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
|
@Volatile
|
||||||
private var activeRenderThread: Thread? = null
|
var TARGET_FPS = 30
|
||||||
|
|
||||||
|
|
||||||
|
private val activeRenderThread: Thread
|
||||||
|
|
||||||
internal val activeRenders: MutableList<Component> = CopyOnWriteArrayList()
|
|
||||||
internal val activeRenderEvents: MutableList<(deltaInNanos: Long)->Unit> = 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()
|
private val renderLoop = ActiveRenderLoop()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the version number.
|
* Gets the version number.
|
||||||
*/
|
*/
|
||||||
const val version = "1.4"
|
const val version = "1.5"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||||
add(SwingActiveRender::class.java, "0dfec3d996f3420d82a864c6cd5a2646", version)
|
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.
|
* @param component the component to add to the ActiveRender thread.
|
||||||
*/
|
*/
|
||||||
fun add(component: Component) {
|
fun add(component: Component) {
|
||||||
SwingUtilities.invokeLater {
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
// this part has to be on the swing EDT
|
// this part has to be on the swing EDT
|
||||||
component.ignoreRepaint = true
|
component.ignoreRepaint = true
|
||||||
}
|
} else {
|
||||||
|
SwingUtilities.invokeAndWait {
|
||||||
|
// this part has to be on the swing EDT
|
||||||
synchronized(renderLoop) {
|
component.ignoreRepaint = true
|
||||||
if (!hasActiveRenders) {
|
|
||||||
hasActiveRenders = true
|
|
||||||
setupActiveRenderThread()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @param component the component to remove
|
||||||
*/
|
*/
|
||||||
fun remove(component: Component) {
|
fun remove(component: Component) {
|
||||||
SwingUtilities.invokeLater {
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
// this part has to be on the swing EDT
|
// this part has to be on the swing EDT
|
||||||
component.ignoreRepaint = false
|
component.ignoreRepaint = false
|
||||||
}
|
} else {
|
||||||
|
SwingUtilities.invokeAndWait {
|
||||||
|
// this part has to be on the swing EDT
|
||||||
synchronized(renderLoop) {
|
component.ignoreRepaint = false
|
||||||
activeRenders.remove(component)
|
|
||||||
|
|
||||||
val hasActiveRenders = activeRenders.isNotEmpty()
|
|
||||||
this.hasActiveRenders = hasActiveRenders
|
|
||||||
if (!hasActiveRenders) {
|
|
||||||
// our thread loop will kill itself
|
|
||||||
activeRenderThread = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderLoop.maybeShutdown(component)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,13 +152,7 @@ object SwingActiveRender {
|
||||||
|
|
||||||
fun isDispatchThread(): Boolean {
|
fun isDispatchThread(): Boolean {
|
||||||
// make sure we are initialized!
|
// make sure we are initialized!
|
||||||
if (activeRenderThread == null) {
|
setupActiveRenderThread()
|
||||||
synchronized(renderLoop) {
|
|
||||||
if (activeRenderThread == null) {
|
|
||||||
setupActiveRenderThread()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Thread.currentThread() == renderLoop.currentThread
|
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
|
* Creates (if necessary) the active-render thread. When there are no active-render targets, this thread will exit
|
||||||
*/
|
*/
|
||||||
private fun setupActiveRenderThread() {
|
private fun setupActiveRenderThread() {
|
||||||
if (activeRenderThread != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
activeRenderThread = Thread(renderLoop, "Swing-ActiveRender")
|
|
||||||
activeRenderThread!!.isDaemon = true
|
|
||||||
activeRenderThread!!.start()
|
|
||||||
|
|
||||||
renderLoop.waitForStartup()
|
renderLoop.waitForStartup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue