Add Olive.reload and Reloadable class for reloadable state

This commit is contained in:
Edwin Jakobs
2019-11-02 12:27:55 +01:00
parent 06d25b2ca5
commit 1c72ad750b
4 changed files with 77 additions and 9 deletions

View File

@@ -36,8 +36,33 @@ Recent versions of `orx-olive` automatically set the `org.openrndr.ignoreShadeSt
makes OPENRNDR ignore errors in the shade style and return the default shader. To get this behaviour in makes OPENRNDR ignore errors in the shade style and return the default shader. To get this behaviour in
older versions add `-Dorg.openrndr.ignoreShadeStyleErrors=true` to the JVM arguments. older versions add `-Dorg.openrndr.ignoreShadeStyleErrors=true` to the JVM arguments.
## Persistent Data ## Reloadable State
Along with the extension comes a mechanism that allows state to be reloaded from a store on script reload.
This functionality is offered by the `Reloadable` class.
An example `live.kts` in which the reloadable state is used:
```kotlin
@file:Suppress("UNUSED_LAMBDA_EXPRESSION")
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
{ program: PersistentProgram ->
program.apply {
val a = object : Reloadable() {
var x : Double = 0.0
}
a.reload()
extend {
// do something with a.x here
}
}
}
```
Keep in mind that `Reloadable` should only be used for singleton classes.
## Persistent Data
Sometimes you want to keep parts of your application persistent. In the following example Sometimes you want to keep parts of your application persistent. In the following example
we show how you can prepare the host program to contain a persistent camera device. we show how you can prepare the host program to contain a persistent camera device.
@@ -61,7 +86,7 @@ fun main() = application{
} }
``` ```
The live script `src/main/PersistentCamera.kt` then looks like this: The live script `src/main/PersistentCamera.kts` then looks like this:
```kotlin ```kotlin
@file:Suppress("UNUSED_LAMBDA_EXPRESSION") @file:Suppress("UNUSED_LAMBDA_EXPRESSION")

View File

@@ -8,7 +8,7 @@ internal class LoadException(message: String? = null, cause: Throwable? = null)
internal class KtsObjectLoader(classLoader: ClassLoader? = Thread.currentThread().contextClassLoader) { internal class KtsObjectLoader(classLoader: ClassLoader? = Thread.currentThread().contextClassLoader) {
val engine = ScriptEngineManager(classLoader).getEngineByExtension("kts") private val engine = ScriptEngineManager(classLoader).getEngineByExtension("kts")
init { init {
if (engine == null) { if (engine == null) {

View File

@@ -5,15 +5,16 @@ import org.openrndr.Program
import org.openrndr.draw.Session import org.openrndr.draw.Session
import org.openrndr.events.Event import org.openrndr.events.Event
import org.operndr.extras.filewatcher.stop import org.operndr.extras.filewatcher.stop
import org.operndr.extras.filewatcher.triggerChange
import org.operndr.extras.filewatcher.watchFile import org.operndr.extras.filewatcher.watchFile
import java.io.File import java.io.File
fun <T> Event<T>.saveListeners(store: MutableMap<Event<*>, List<(Any) -> Unit>>) { private fun <T> Event<T>.saveListeners(store: MutableMap<Event<*>, List<(Any) -> Unit>>) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
store[this] = listeners.map { it } as List<(Any) -> Unit> store[this] = listeners.map { it } as List<(Any) -> Unit>
} }
fun <T> Event<T>.restoreListeners(store: Map<Event<*>, List<(Any) -> Unit>>) { private fun <T> Event<T>.restoreListeners(store: Map<Event<*>, List<(Any) -> Unit>>) {
listeners.retainAll(store[this] ?: emptyList<T>()) listeners.retainAll(store[this] ?: emptyList<T>())
} }
@@ -29,16 +30,21 @@ class Olive<P : Program> : Extension {
scriptChange(value) scriptChange(value)
} }
/**
* reloads the active script
*/
fun reload() {
watcher?.triggerChange()
}
private var watcher: (() -> Unit)? = null
override fun setup(program: Program) { override fun setup(program: Program) {
System.setProperty("idea.io.use.fallback", "true") System.setProperty("idea.io.use.fallback", "true")
System.setProperty("org.openrndr.ignoreShadeStyleErrors", "true") System.setProperty("org.openrndr.ignoreShadeStyleErrors", "true")
var watcher: (() -> Unit)? = null
val store = mutableMapOf<Event<*>, List<(Any) -> Unit>>() val store = mutableMapOf<Event<*>, List<(Any) -> Unit>>()
val originalExtensions = program.extensions.map { it } val originalExtensions = program.extensions.map { it }
val trackedListeners = listOf<Event<*>>(program.mouse.buttonDown, val trackedListeners = listOf<Event<*>>(program.mouse.buttonDown,
program.mouse.buttonUp, program.mouse.buttonUp,
program.mouse.clicked, program.mouse.clicked,

View File

@@ -0,0 +1,37 @@
package org.openrndr.extra.olive
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.jvmName
private val store = mutableMapOf<String, Any>()
/**
* A class with which persistent state can be reloaded from inside Olive scripts.
*/
open class Reloadable {
/**
* reload property values from store
*/
@Suppress("UNCHECKED_CAST")
fun reload() {
val existing = store[this::class.jvmName]
if (existing != null) {
for (p in this::class.declaredMemberProperties) {
val e = existing::class.declaredMemberProperties.find { it.name == p.name }
if (e != null) {
try {
val value = (e as KProperty1<Any, Any?>).get(existing)
val mp = (p as KMutableProperty1<Any, Any?>)
mp.set(this, value as Any)
println("reloaded property ${p.name} <- ${value}")
} catch (e: Throwable) {
println("error while reloading property ${p.name}: ${e.message}")
}
}
}
}
store[this::class.jvmName] = this
}
}