diff --git a/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcher.kt b/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcher.kt index e35a0d71..e8feb935 100644 --- a/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcher.kt +++ b/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcher.kt @@ -3,9 +3,7 @@ package org.openrndr.extra.filewatcher import com.sun.nio.file.SensitivityWatchEventModifier import kotlinx.coroutines.* import mu.KotlinLogging -import org.openrndr.Program import org.openrndr.events.Event -import org.openrndr.launch import java.io.File import java.nio.file.FileSystems import java.nio.file.Path @@ -13,7 +11,6 @@ import java.nio.file.StandardWatchEventKinds import java.nio.file.WatchKey import java.util.WeakHashMap import kotlin.concurrent.thread -import kotlin.reflect.KProperty private val logger = KotlinLogging.logger {} @@ -52,15 +49,19 @@ private val watchThread by lazy { } } - +/** + * @property file + * @property fileChangedEvent + * @param requestStopEvent + */ class FileWatcher( - val file: File, + private val file: File, private val fileChangedEvent: Event, requestStopEvent: Event? = null ) { - val path = file.absoluteFile.toPath() - val parent = path.parent - val key = pathKeys.getOrPut(parent) { + private val path = file.absoluteFile.toPath() + private val parent = path.parent + private val key = pathKeys.getOrPut(parent) { parent.register( watchService, arrayOf(StandardWatchEventKinds.ENTRY_MODIFY), SensitivityWatchEventModifier.HIGH @@ -78,6 +79,7 @@ class FileWatcher( } } + @Suppress("MemberVisibilityCanBePrivate") fun stop() { synchronized(watching) { logger.info { "stopping, watcher stop requested" } @@ -90,34 +92,36 @@ class FileWatcher( } } - - -fun watchFile( +/** + * Watch a file for changes + * @param file the file to watch + * @param valueChangedEvent the event that is triggered when the value (after transforming) has changed + * @param requestStopEvent an event that can be triggered to request the watcher to stop + * @param transducer a function that transforms a [File] into a value of type [R] + */ +fun watchFile( file: File, - contentsChangedEvent: Event? = null, + valueChangedEvent: Event? = null, requestStopEvent: Event? = null, - transducer: (File) -> T -): () -> T { + transducer: (File) -> R +): () -> R { var result = transducer(file) val fileChangedEvent = Event() - val watcher = FileWatcher(file, fileChangedEvent, requestStopEvent) + + @Suppress("UNUSED_VARIABLE") val watcher = FileWatcher(file, fileChangedEvent, requestStopEvent) fileChangedEvent.listen { + @Suppress("MemberVisibilityCanBePrivate") try { result = transducer(file) - contentsChangedEvent?.trigger(result) + valueChangedEvent?.trigger(result) } catch (e: Throwable) { logger.error(e) { - """exception while transducing file""" + """exception while transforming file ${file.absolutePath}""" } } } return { result } -} - - -//@JvmName("programWatchFile") -//fun Program.watchFile(file: File, onChange: Event? = null, transducer: (File) -> T): () -> T = -// watchFile(this, file, onChange, transducer = transducer) +} \ No newline at end of file diff --git a/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcherDelegate.kt b/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcherDelegate.kt index 3330a72f..54dd9653 100644 --- a/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcherDelegate.kt +++ b/orx-jvm/orx-file-watcher/src/main/kotlin/FileWatcherDelegate.kt @@ -1,11 +1,21 @@ +package org.openrndr.extra.filewatcher + import kotlinx.coroutines.yield import org.openrndr.Program import org.openrndr.events.Event -import org.openrndr.extra.filewatcher.watchFile import org.openrndr.launch import java.io.File import kotlin.reflect.KProperty +/** + * Property delegator that watches a file. Changes are propagated right before the [Program] updates its extensions + * @param program the program to synchronise updates with + * @param file the file to watch + * @param valueChangedEvent the event that is triggered when the value (after transformation) has changed + * @param requestStopEvent an event that can be triggered to request the watcher to stop + * @since 0.4.3 + * @see watchingFile + */ class FileWatcherDelegate( program: Program, file: File, @@ -13,10 +23,11 @@ class FileWatcherDelegate( requestStopEvent: Event? = null, transducer: (File) -> T ) { - val watchValue = watchFile(file, valueChangedEvent, requestStopEvent, transducer) - var value = watchValue() + private val watchValue = watchFile(file, valueChangedEvent, requestStopEvent, transducer) + private var value = watchValue() init { + // make sure that `value` is updated at the beginning of a draw cycle and not mid-cycle. program.launch { while (true) { value = watchValue() @@ -25,11 +36,23 @@ class FileWatcherDelegate( } } + /** + * Return transformed value + */ operator fun getValue(any: Any?, property: KProperty<*>): T { return value } } +/** + * Delegate value to a file watcher + * @param file the file to watch + * @param valueChangedEvent the event that is triggered when the value (after transformation) has changed + * @param requestStopEvent an event that can be triggered to request the watcher to stop + * @param transducer a function that transforms a [File] into a value of type [R] + * @since 0.4.3 + * @see FileWatcherDelegate + */ fun Program.watchingFile( file: File, valueChangedEvent: Event? = null, diff --git a/orx-property-watchers/src/commonMain/kotlin/PropertyWatcherDelegates.kt b/orx-property-watchers/src/commonMain/kotlin/PropertyWatcherDelegates.kt index 70087bce..8c9aefe6 100644 --- a/orx-property-watchers/src/commonMain/kotlin/PropertyWatcherDelegates.kt +++ b/orx-property-watchers/src/commonMain/kotlin/PropertyWatcherDelegates.kt @@ -7,10 +7,11 @@ import kotlin.reflect.KProperty0 /** * Property watcher delegate * @see watchingProperty + * @since 0.4.3 */ class PropertyWatcherDelegate( private val property: KProperty0, - private val valueChangedEvent: Event, + private val valueChangedEvent: Event? = null, private val cleaner: ((R) -> Unit)? = null, val function: (V) -> R ) { @@ -24,19 +25,21 @@ class PropertyWatcherDelegate( cleaner?.invoke(it) } value = function(ref) + valueChangedEvent?.trigger(value ?: error("no value")) } return value ?: error("no value?") } - } /** * Property watcher delegate * @see watchingProperties + * @since 0.4.3 */ class PropertyWatcherDelegate2( private val toWatch0: KProperty0, private val toWatch1: KProperty0, + private val valueChangedEvent: Event? = null, private val cleaner: ((R) -> Unit)? = null, private val function: (V0, V1) -> R ) { @@ -54,6 +57,7 @@ class PropertyWatcherDelegate2( cleaner?.invoke(it) } value = function(ref0, ref1) + valueChangedEvent?.trigger(value ?: error("no value")) } return value ?: error("no value?") } @@ -62,11 +66,13 @@ class PropertyWatcherDelegate2( /** * Property watcher delegate * @see watchingProperties + * @since 0.4.3 */ class PropertyWatcherDelegate3( private val toWatch0: KProperty0, private val toWatch1: KProperty0, private val toWatch2: KProperty0, + private val valueChangedEvent: Event? = null, private val cleaner: ((R) -> Unit)? = null, private val function: (V0, V1, V2) -> R ) { @@ -85,6 +91,7 @@ class PropertyWatcherDelegate3( cleaner?.invoke(it) } value = function(ref0, ref1, ref2) + valueChangedEvent?.trigger(value ?: error("no value")) } return value ?: error("no value?") } @@ -94,30 +101,33 @@ class PropertyWatcherDelegate3( /** * Delegate property value to a function for which the value of a single property is watched * @param property the property for which to watch for value changes + * @param valueChangedEvent an optional event that is triggered on value change + * @param cleaner an optional cleaner function that is invoked to clean up the old value * @param function a function that maps the property value to a new value + * @since 0.4.3 */ fun watchingProperty( property: KProperty0, + valueChangedEvent: Event? = null, cleaner: ((R) -> Unit)? = null, function: (value: V) -> R -): PropertyWatcherDelegate { - return PropertyWatcherDelegate(property, Event("value-changed-${property.name}"), cleaner, function) -} +): PropertyWatcherDelegate = PropertyWatcherDelegate(property, valueChangedEvent, cleaner, function) /** * Delegate property value to a function for which the values of 2 properties are watched * @param property0 the first property for which to watch for value changes * @param property1 the second property which to watch for value changes * @param function a function that maps the two property values to a new value + * @since 0.4.3 */ fun watchingProperties( property0: KProperty0, property1: KProperty0, + valueChangedEvent: Event? = null, cleaner: ((R) -> Unit)?, function: (value0: V0, value1: V1) -> R -): PropertyWatcherDelegate2 { - return PropertyWatcherDelegate2(property0, property1, cleaner, function) -} +): PropertyWatcherDelegate2 = + PropertyWatcherDelegate2(property0, property1, valueChangedEvent, cleaner, function) /** * Delegate property value to a function for which the values of 3 properties are watched @@ -125,13 +135,14 @@ fun watchingProperties( * @param property1 the second property which to watch for value changes * @param property2 the third property which to watch for value changes * @param function a function that maps the three property values to a new value + * @since 0.4.3 */ fun watchingProperties( property0: KProperty0, property1: KProperty0, property2: KProperty0, + valueChangedEvent: Event? = null, cleaner: ((R) -> Unit)? = null, function: (value0: V0, value1: V1, value2: V2) -> R -): PropertyWatcherDelegate3 { - return PropertyWatcherDelegate3(property0, property1, property2, cleaner, function) -} \ No newline at end of file +): PropertyWatcherDelegate3 = + PropertyWatcherDelegate3(property0, property1, property2, valueChangedEvent, cleaner, function) \ No newline at end of file diff --git a/orx-property-watchers/src/jvmMain/kotlin/ImagePath.kt b/orx-property-watchers/src/jvmMain/kotlin/ImagePath.kt index a189ccb8..c7d53d3e 100644 --- a/orx-property-watchers/src/jvmMain/kotlin/ImagePath.kt +++ b/orx-property-watchers/src/jvmMain/kotlin/ImagePath.kt @@ -1,12 +1,17 @@ package org.openrndr.extra.propertywatchers -import org.openrndr.Program import org.openrndr.draw.ColorBuffer import org.openrndr.draw.loadImage import java.io.File import kotlin.reflect.KProperty0 -fun Program.watchingImagePath(pathProperty: KProperty0, imageTransform: (ColorBuffer) -> ColorBuffer = { it }) = +/** + * Delegate property value by watching a path property + * @param pathProperty the property holding a path to watch + * @param imageTransform an optional image transform function + * @since 0.4.3 + */ +fun watchingImagePath(pathProperty: KProperty0, imageTransform: (ColorBuffer) -> ColorBuffer = { it }) = watchingProperty(pathProperty, cleaner = { it.destroy() }) { val file = File(it) require(file.exists()) { "$it does not exist" } @@ -17,4 +22,4 @@ fun Program.watchingImagePath(pathProperty: KProperty0, imageTransform: image.destroy() } transformedImage - } + } \ No newline at end of file