[orx-file-watcher, orx-property-watchers] Add events and documentation

This commit is contained in:
Edwin Jakobs
2023-03-19 09:00:37 +01:00
parent e820885ea4
commit f21422359a
4 changed files with 83 additions and 40 deletions

View File

@@ -3,9 +3,7 @@ package org.openrndr.extra.filewatcher
import com.sun.nio.file.SensitivityWatchEventModifier import com.sun.nio.file.SensitivityWatchEventModifier
import kotlinx.coroutines.* import kotlinx.coroutines.*
import mu.KotlinLogging import mu.KotlinLogging
import org.openrndr.Program
import org.openrndr.events.Event import org.openrndr.events.Event
import org.openrndr.launch
import java.io.File import java.io.File
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@@ -13,7 +11,6 @@ import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchKey import java.nio.file.WatchKey
import java.util.WeakHashMap import java.util.WeakHashMap
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.reflect.KProperty
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
@@ -52,15 +49,19 @@ private val watchThread by lazy {
} }
} }
/**
* @property file
* @property fileChangedEvent
* @param requestStopEvent
*/
class FileWatcher( class FileWatcher(
val file: File, private val file: File,
private val fileChangedEvent: Event<File>, private val fileChangedEvent: Event<File>,
requestStopEvent: Event<Unit>? = null requestStopEvent: Event<Unit>? = null
) { ) {
val path = file.absoluteFile.toPath() private val path = file.absoluteFile.toPath()
val parent = path.parent private val parent = path.parent
val key = pathKeys.getOrPut(parent) { private val key = pathKeys.getOrPut(parent) {
parent.register( parent.register(
watchService, arrayOf(StandardWatchEventKinds.ENTRY_MODIFY), watchService, arrayOf(StandardWatchEventKinds.ENTRY_MODIFY),
SensitivityWatchEventModifier.HIGH SensitivityWatchEventModifier.HIGH
@@ -78,6 +79,7 @@ class FileWatcher(
} }
} }
@Suppress("MemberVisibilityCanBePrivate")
fun stop() { fun stop() {
synchronized(watching) { synchronized(watching) {
logger.info { "stopping, watcher stop requested" } logger.info { "stopping, watcher stop requested" }
@@ -90,34 +92,36 @@ class FileWatcher(
} }
} }
/**
* Watch a file for changes
fun <T> watchFile( * @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 <R> watchFile(
file: File, file: File,
contentsChangedEvent: Event<T>? = null, valueChangedEvent: Event<R>? = null,
requestStopEvent: Event<Unit>? = null, requestStopEvent: Event<Unit>? = null,
transducer: (File) -> T transducer: (File) -> R
): () -> T { ): () -> R {
var result = transducer(file) var result = transducer(file)
val fileChangedEvent = Event<File>() val fileChangedEvent = Event<File>()
val watcher = FileWatcher(file, fileChangedEvent, requestStopEvent)
@Suppress("UNUSED_VARIABLE") val watcher = FileWatcher(file, fileChangedEvent, requestStopEvent)
fileChangedEvent.listen { fileChangedEvent.listen {
@Suppress("MemberVisibilityCanBePrivate")
try { try {
result = transducer(file) result = transducer(file)
contentsChangedEvent?.trigger(result) valueChangedEvent?.trigger(result)
} catch (e: Throwable) { } catch (e: Throwable) {
logger.error(e) { logger.error(e) {
"""exception while transducing file""" """exception while transforming file ${file.absolutePath}"""
} }
} }
} }
return { return {
result result
} }
} }
//@JvmName("programWatchFile")
//fun <T> Program.watchFile(file: File, onChange: Event<T>? = null, transducer: (File) -> T): () -> T =
// watchFile(this, file, onChange, transducer = transducer)

View File

@@ -1,11 +1,21 @@
package org.openrndr.extra.filewatcher
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.openrndr.Program import org.openrndr.Program
import org.openrndr.events.Event import org.openrndr.events.Event
import org.openrndr.extra.filewatcher.watchFile
import org.openrndr.launch import org.openrndr.launch
import java.io.File import java.io.File
import kotlin.reflect.KProperty 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<T>( class FileWatcherDelegate<T>(
program: Program, program: Program,
file: File, file: File,
@@ -13,10 +23,11 @@ class FileWatcherDelegate<T>(
requestStopEvent: Event<Unit>? = null, requestStopEvent: Event<Unit>? = null,
transducer: (File) -> T transducer: (File) -> T
) { ) {
val watchValue = watchFile(file, valueChangedEvent, requestStopEvent, transducer) private val watchValue = watchFile(file, valueChangedEvent, requestStopEvent, transducer)
var value = watchValue() private var value = watchValue()
init { init {
// make sure that `value` is updated at the beginning of a draw cycle and not mid-cycle.
program.launch { program.launch {
while (true) { while (true) {
value = watchValue() value = watchValue()
@@ -25,11 +36,23 @@ class FileWatcherDelegate<T>(
} }
} }
/**
* Return transformed value
*/
operator fun getValue(any: Any?, property: KProperty<*>): T { operator fun getValue(any: Any?, property: KProperty<*>): T {
return value 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 <R> Program.watchingFile( fun <R> Program.watchingFile(
file: File, file: File,
valueChangedEvent: Event<R>? = null, valueChangedEvent: Event<R>? = null,

View File

@@ -7,10 +7,11 @@ import kotlin.reflect.KProperty0
/** /**
* Property watcher delegate * Property watcher delegate
* @see watchingProperty * @see watchingProperty
* @since 0.4.3
*/ */
class PropertyWatcherDelegate<V, R>( class PropertyWatcherDelegate<V, R>(
private val property: KProperty0<V>, private val property: KProperty0<V>,
private val valueChangedEvent: Event<V>, private val valueChangedEvent: Event<R>? = null,
private val cleaner: ((R) -> Unit)? = null, private val cleaner: ((R) -> Unit)? = null,
val function: (V) -> R val function: (V) -> R
) { ) {
@@ -24,19 +25,21 @@ class PropertyWatcherDelegate<V, R>(
cleaner?.invoke(it) cleaner?.invoke(it)
} }
value = function(ref) value = function(ref)
valueChangedEvent?.trigger(value ?: error("no value"))
} }
return value ?: error("no value?") return value ?: error("no value?")
} }
} }
/** /**
* Property watcher delegate * Property watcher delegate
* @see watchingProperties * @see watchingProperties
* @since 0.4.3
*/ */
class PropertyWatcherDelegate2<V0, V1, R>( class PropertyWatcherDelegate2<V0, V1, R>(
private val toWatch0: KProperty0<V0>, private val toWatch0: KProperty0<V0>,
private val toWatch1: KProperty0<V1>, private val toWatch1: KProperty0<V1>,
private val valueChangedEvent: Event<R>? = null,
private val cleaner: ((R) -> Unit)? = null, private val cleaner: ((R) -> Unit)? = null,
private val function: (V0, V1) -> R private val function: (V0, V1) -> R
) { ) {
@@ -54,6 +57,7 @@ class PropertyWatcherDelegate2<V0, V1, R>(
cleaner?.invoke(it) cleaner?.invoke(it)
} }
value = function(ref0, ref1) value = function(ref0, ref1)
valueChangedEvent?.trigger(value ?: error("no value"))
} }
return value ?: error("no value?") return value ?: error("no value?")
} }
@@ -62,11 +66,13 @@ class PropertyWatcherDelegate2<V0, V1, R>(
/** /**
* Property watcher delegate * Property watcher delegate
* @see watchingProperties * @see watchingProperties
* @since 0.4.3
*/ */
class PropertyWatcherDelegate3<V0, V1, V2, R>( class PropertyWatcherDelegate3<V0, V1, V2, R>(
private val toWatch0: KProperty0<V0>, private val toWatch0: KProperty0<V0>,
private val toWatch1: KProperty0<V1>, private val toWatch1: KProperty0<V1>,
private val toWatch2: KProperty0<V2>, private val toWatch2: KProperty0<V2>,
private val valueChangedEvent: Event<R>? = null,
private val cleaner: ((R) -> Unit)? = null, private val cleaner: ((R) -> Unit)? = null,
private val function: (V0, V1, V2) -> R private val function: (V0, V1, V2) -> R
) { ) {
@@ -85,6 +91,7 @@ class PropertyWatcherDelegate3<V0, V1, V2, R>(
cleaner?.invoke(it) cleaner?.invoke(it)
} }
value = function(ref0, ref1, ref2) value = function(ref0, ref1, ref2)
valueChangedEvent?.trigger(value ?: error("no value"))
} }
return value ?: error("no value?") return value ?: error("no value?")
} }
@@ -94,30 +101,33 @@ class PropertyWatcherDelegate3<V0, V1, V2, R>(
/** /**
* Delegate property value to a function for which the value of a single property is watched * 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 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 * @param function a function that maps the property value to a new value
* @since 0.4.3
*/ */
fun <V, R> watchingProperty( fun <V, R> watchingProperty(
property: KProperty0<V>, property: KProperty0<V>,
valueChangedEvent: Event<R>? = null,
cleaner: ((R) -> Unit)? = null, cleaner: ((R) -> Unit)? = null,
function: (value: V) -> R function: (value: V) -> R
): PropertyWatcherDelegate<V, R> { ): PropertyWatcherDelegate<V, R> = PropertyWatcherDelegate(property, valueChangedEvent, cleaner, function)
return PropertyWatcherDelegate(property, Event("value-changed-${property.name}"), cleaner, function)
}
/** /**
* Delegate property value to a function for which the values of 2 properties are watched * 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 property0 the first property for which to watch for value changes
* @param property1 the second property 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 * @param function a function that maps the two property values to a new value
* @since 0.4.3
*/ */
fun <V0, V1, R> watchingProperties( fun <V0, V1, R> watchingProperties(
property0: KProperty0<V0>, property0: KProperty0<V0>,
property1: KProperty0<V1>, property1: KProperty0<V1>,
valueChangedEvent: Event<R>? = null,
cleaner: ((R) -> Unit)?, cleaner: ((R) -> Unit)?,
function: (value0: V0, value1: V1) -> R function: (value0: V0, value1: V1) -> R
): PropertyWatcherDelegate2<V0, V1, R> { ): PropertyWatcherDelegate2<V0, V1, R> =
return PropertyWatcherDelegate2(property0, property1, cleaner, function) PropertyWatcherDelegate2(property0, property1, valueChangedEvent, cleaner, function)
}
/** /**
* Delegate property value to a function for which the values of 3 properties are watched * Delegate property value to a function for which the values of 3 properties are watched
@@ -125,13 +135,14 @@ fun <V0, V1, R> watchingProperties(
* @param property1 the second property which to watch for value changes * @param property1 the second property which to watch for value changes
* @param property2 the third 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 * @param function a function that maps the three property values to a new value
* @since 0.4.3
*/ */
fun <V0, V1, V2, R> watchingProperties( fun <V0, V1, V2, R> watchingProperties(
property0: KProperty0<V0>, property0: KProperty0<V0>,
property1: KProperty0<V1>, property1: KProperty0<V1>,
property2: KProperty0<V2>, property2: KProperty0<V2>,
valueChangedEvent: Event<R>? = null,
cleaner: ((R) -> Unit)? = null, cleaner: ((R) -> Unit)? = null,
function: (value0: V0, value1: V1, value2: V2) -> R function: (value0: V0, value1: V1, value2: V2) -> R
): PropertyWatcherDelegate3<V0, V1, V2, R> { ): PropertyWatcherDelegate3<V0, V1, V2, R> =
return PropertyWatcherDelegate3(property0, property1, property2, cleaner, function) PropertyWatcherDelegate3(property0, property1, property2, valueChangedEvent, cleaner, function)
}

View File

@@ -1,12 +1,17 @@
package org.openrndr.extra.propertywatchers package org.openrndr.extra.propertywatchers
import org.openrndr.Program
import org.openrndr.draw.ColorBuffer import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.loadImage import org.openrndr.draw.loadImage
import java.io.File import java.io.File
import kotlin.reflect.KProperty0 import kotlin.reflect.KProperty0
fun Program.watchingImagePath(pathProperty: KProperty0<String>, 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<String>, imageTransform: (ColorBuffer) -> ColorBuffer = { it }) =
watchingProperty(pathProperty, cleaner = { it.destroy() }) { watchingProperty(pathProperty, cleaner = { it.destroy() }) {
val file = File(it) val file = File(it)
require(file.exists()) { "$it does not exist" } require(file.exists()) { "$it does not exist" }
@@ -17,4 +22,4 @@ fun Program.watchingImagePath(pathProperty: KProperty0<String>, imageTransform:
image.destroy() image.destroy()
} }
transformedImage transformedImage
} }