Added support for window/canvas page flipping. Code cleanup/org.
parent
0723fc432c
commit
6851b1483b
|
@ -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<Component> = 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()
|
||||
}
|
||||
|
|
|
@ -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<Component> = 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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue