From 1c72ad750b17fe14eea6ee8c1313fbd5ca856967 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Sat, 2 Nov 2019 12:27:55 +0100 Subject: [PATCH] Add Olive.reload and Reloadable class for reloadable state --- orx-olive/README.md | 29 +++++++++++++-- orx-olive/src/main/kotlin/KtsObjectLoader.kt | 2 +- orx-olive/src/main/kotlin/Olive.kt | 18 ++++++---- orx-olive/src/main/kotlin/Reloadable.kt | 37 ++++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 orx-olive/src/main/kotlin/Reloadable.kt diff --git a/orx-olive/README.md b/orx-olive/README.md index 74c5d4ab..18cdeac5 100644 --- a/orx-olive/README.md +++ b/orx-olive/README.md @@ -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 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 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 @file:Suppress("UNUSED_LAMBDA_EXPRESSION") diff --git a/orx-olive/src/main/kotlin/KtsObjectLoader.kt b/orx-olive/src/main/kotlin/KtsObjectLoader.kt index a1207e7d..0f1e5f0f 100644 --- a/orx-olive/src/main/kotlin/KtsObjectLoader.kt +++ b/orx-olive/src/main/kotlin/KtsObjectLoader.kt @@ -8,7 +8,7 @@ internal class LoadException(message: String? = null, cause: Throwable? = null) internal class KtsObjectLoader(classLoader: ClassLoader? = Thread.currentThread().contextClassLoader) { - val engine = ScriptEngineManager(classLoader).getEngineByExtension("kts") + private val engine = ScriptEngineManager(classLoader).getEngineByExtension("kts") init { if (engine == null) { diff --git a/orx-olive/src/main/kotlin/Olive.kt b/orx-olive/src/main/kotlin/Olive.kt index e19f7f6d..ba35d555 100644 --- a/orx-olive/src/main/kotlin/Olive.kt +++ b/orx-olive/src/main/kotlin/Olive.kt @@ -5,15 +5,16 @@ import org.openrndr.Program import org.openrndr.draw.Session import org.openrndr.events.Event import org.operndr.extras.filewatcher.stop +import org.operndr.extras.filewatcher.triggerChange import org.operndr.extras.filewatcher.watchFile import java.io.File -fun Event.saveListeners(store: MutableMap, List<(Any) -> Unit>>) { +private fun Event.saveListeners(store: MutableMap, List<(Any) -> Unit>>) { @Suppress("UNCHECKED_CAST") store[this] = listeners.map { it } as List<(Any) -> Unit> } -fun Event.restoreListeners(store: Map, List<(Any) -> Unit>>) { +private fun Event.restoreListeners(store: Map, List<(Any) -> Unit>>) { listeners.retainAll(store[this] ?: emptyList()) } @@ -29,16 +30,21 @@ class Olive

: Extension { scriptChange(value) } + /** + * reloads the active script + */ + fun reload() { + watcher?.triggerChange() + } + + private var watcher: (() -> Unit)? = null + override fun setup(program: Program) { System.setProperty("idea.io.use.fallback", "true") System.setProperty("org.openrndr.ignoreShadeStyleErrors", "true") - var watcher: (() -> Unit)? = null - val store = mutableMapOf, List<(Any) -> Unit>>() - val originalExtensions = program.extensions.map { it } - val trackedListeners = listOf>(program.mouse.buttonDown, program.mouse.buttonUp, program.mouse.clicked, diff --git a/orx-olive/src/main/kotlin/Reloadable.kt b/orx-olive/src/main/kotlin/Reloadable.kt new file mode 100644 index 00000000..f801c4d0 --- /dev/null +++ b/orx-olive/src/main/kotlin/Reloadable.kt @@ -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() + +/** + * 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).get(existing) + val mp = (p as KMutableProperty1) + 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 + } +}