Verified Commit 0cd05490 authored by Konstantin Kopper's avatar Konstantin Kopper
Browse files

Improved usage of coroutines for observing global variables

parent e2d084e0
Pipeline #29008 passed with stages
in 6 minutes and 2 seconds
......@@ -10,6 +10,9 @@ import javafx.scene.Scene
import javafx.scene.image.Image
import javafx.stage.Stage
import javafx.stage.Window
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import pseuco.javaCompiler.debugger.PseuCoDebugger
import pseuco.javaCompiler.debugger.ThreadListener
import util.ReflectionUtils
......@@ -23,6 +26,8 @@ import java.util.concurrent.atomic.AtomicBoolean
class FXDebugger(toBeExecuted: File, out: OutputStream, err: OutputStream, owner: Window) :
PseuCoDebugger(toBeExecuted, out, err) {
private val scope = CoroutineScope(Dispatchers.Default)
private var stage: Stage = Stage().apply { initOwner(owner) }
private val debuggerPane = DebuggerPane(this, stage)
......@@ -91,9 +96,9 @@ class FXDebugger(toBeExecuted: File, out: OutputStream, err: OutputStream, owner
// Global variables, wrapped in reflection properties
val fields = c.declaredFields
val bools = fields.filter { it.type == Boolean::class.java }.map { ReflectionBooleanProperty(it, c) }
val ints = fields.filter { it.type == Int::class.java }.map { ReflectionIntegerProperty(it, c) }
val strings = fields.filter { it.type == String::class.java }.map { ReflectionStringProperty(it, c) }
val bools = fields.filter { it.type == Boolean::class.java }.map { ReflectionBooleanProperty(it, c, scope) }
val ints = fields.filter { it.type == Int::class.java }.map { ReflectionIntegerProperty(it, c, scope) }
val strings = fields.filter { it.type == String::class.java }.map { ReflectionStringProperty(it, c, scope) }
debuggerPane.variablesPane.also {
it.addBooleans(bools)
......@@ -116,6 +121,8 @@ class FXDebugger(toBeExecuted: File, out: OutputStream, err: OutputStream, owner
return@Thread // Thread was interrupted, so just terminate it.
e.printStackTrace()
} finally {
scope.cancel()
}
}, "mainAgent")
......@@ -125,6 +132,7 @@ class FXDebugger(toBeExecuted: File, out: OutputStream, err: OutputStream, owner
// Show debugger view
Platform.runLater {
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
this@FXDebugger.stage.apply {
scene = Scene(debuggerPane)
onCloseRequest = EventHandler { this@FXDebugger.interrupt() }
......
package fxGui.debugger.variables
import javafx.beans.property.ReadOnlyBooleanPropertyBase
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.lang.reflect.Field
......@@ -19,9 +19,10 @@ import java.lang.reflect.Field
class ReflectionBooleanProperty(
f: Field,
instance: Class<*>,
scope: CoroutineScope,
private val name: String = f.name,
private val bean: Any? = null
) : ReadOnlyBooleanPropertyBase(), ReflectionProperty<Boolean> {
) : ReadOnlyBooleanPropertyBase(), ReflectionProperty<Boolean>, CoroutineScope by scope {
/**
* Function which produces the current value of the underlying field.
......@@ -31,24 +32,16 @@ class ReflectionBooleanProperty(
*/
private val getter: () -> Boolean = { f.getBoolean(instance) }
/**
* Asynchronous observer checking if the underlying value has changed. If so, all listeners are informed.
*
* @author Konstantin Kopper
* @since 2.0.0
*/
private val observer = GlobalScope.launch { observe(::fireValueChangedEvent) }
init {
require(f.type == Boolean::class.java) { "Given field does not have boolean type." }
require(f in instance.fields) { "Given field not found in concrete instance." }
// Coroutine can be started here. Cancellation ensured by implementing CoroutineScope.
// For debugging: add CoroutineName("observer_bool:${f.name}") as context parameter
launch { super.observe(::fireValueChangedEvent) }
}
override fun get(): Boolean = getter()
override fun getName(): String = name
override fun getBean(): Any? = bean
override fun stopObserve() {
observer.cancel()
}
}
package fxGui.debugger.variables
import javafx.beans.property.ReadOnlyIntegerPropertyBase
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.lang.reflect.Field
......@@ -19,9 +19,10 @@ import java.lang.reflect.Field
class ReflectionIntegerProperty(
f: Field,
instance: Class<*>,
scope: CoroutineScope,
private val name: String = f.name,
private val bean: Any? = null
) : ReadOnlyIntegerPropertyBase(), ReflectionProperty<Int> {
) : ReadOnlyIntegerPropertyBase(), ReflectionProperty<Int>, CoroutineScope by scope {
/**
* Function which produces the current value of the underlying field.
......@@ -31,24 +32,16 @@ class ReflectionIntegerProperty(
*/
private val getter: () -> Int = { f.getInt(instance) }
/**
* Asynchronous observer checking if the underlying value has changed. If so, all listeners are informed.
*
* @author Konstantin Kopper
* @since 2.0.0
*/
private val observer = GlobalScope.launch { super.observe(::fireValueChangedEvent) }
init {
require(f.type == Int::class.java) { "Given field does not have integer type." }
require(f in instance.fields) { "Given field not found in concrete instance." }
// Coroutine can be started here. Cancellation ensured by implementing CoroutineScope.
// For debugging: add CoroutineName("observer_int:${f.name}") as context parameter.
launch { super.observe(::fireValueChangedEvent) }
}
override fun get(): Int = getter()
override fun getName(): String = name
override fun getBean(): Any? = bean
override fun stopObserve() {
observer.cancel()
}
}
package fxGui.debugger.variables
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
/**
* Common interface of all reflection based properties.
*
......@@ -7,7 +11,7 @@ package fxGui.debugger.variables
* @since 2.0.0
* @property T The type of the property.
*/
interface ReflectionProperty<T> {
internal interface ReflectionProperty<T> : CoroutineScope {
/**
* Return the current value of the property.
......@@ -21,28 +25,16 @@ interface ReflectionProperty<T> {
* @author Konstantin Kopper
* @since 2.0.0
*/
fun observe(callback: () -> Unit) {
var oldValue: T = get()
while (!Thread.currentThread().isInterrupted) {
suspend fun observe(callback: () -> Unit) {
var oldValue = get()
while (isActive) {
val currentValue = get()
if (oldValue != currentValue) {
callback()
oldValue = currentValue
}
try {
Thread.sleep(250)
} catch (e: InterruptedException) {
break
}
delay(250)
}
}
/**
* Stop the observation.
*
* @author Konstantin Kopper
* @since 2.0.0
* @see observe
*/
fun stopObserve()
}
package fxGui.debugger.variables
import javafx.beans.property.ReadOnlyStringPropertyBase
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.lang.reflect.Field
......@@ -19,9 +19,10 @@ import java.lang.reflect.Field
class ReflectionStringProperty(
f: Field,
instance: Class<*>,
scope: CoroutineScope,
private val name: String = f.name,
private val bean: Any? = null
) : ReadOnlyStringPropertyBase(), ReflectionProperty<String> {
) : ReadOnlyStringPropertyBase(), ReflectionProperty<String>, CoroutineScope by scope {
/**
* Function which produces the current value of the underlying field.
......@@ -31,24 +32,16 @@ class ReflectionStringProperty(
*/
private val getter: () -> String = { f.get(instance) as String }
/**
* Asynchronous observer checking if the underlying value has changed. If so, all listeners are informed.
*
* @author Konstantin Kopper
* @since 2.0.0
*/
private val observer = GlobalScope.launch { observe(::fireValueChangedEvent) }
init {
require(f.type == String::class.java) { "Given field does not have string type." }
require(f in instance.fields) { "Given field not found in concrete instance." }
// Coroutine can be started here. Cancellation ensured by implementing CoroutineScope.
// For debugging: add CoroutineName("observe_string:${f.name}") as context parameter
launch { super.observe(::fireValueChangedEvent) }
}
override fun get(): String = getter()
override fun getName(): String = name
override fun getBean(): Any? = bean
override fun stopObserve() {
observer.cancel()
}
}
......@@ -64,7 +64,7 @@ class VariablesPane : AnchorPane() {
* @author Konstantin Kopper
* @since 2.0.0
*/
private var properties: List<ReflectionProperty<*>> = emptyList()
private val properties: MutableList<ReflectionProperty<*>> = mutableListOf()
/**
* List of [GlobalVariable]s as shown in the table.
......@@ -123,10 +123,6 @@ class VariablesPane : AnchorPane() {
data += FXCollections.observableList(list.map { GlobalVariable(it) })
}
fun stopListeners() {
properties.forEach { it.stopObserve() }
}
/**
* Binds the width of [valueColumn] to the width of [stage] (minus the width of [typeColumn] and [nameColumn]).
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment