[orx-parameters, orx-property-watchers, orx-file-watcher, orx-gui] Add @PathParameter, file watcher delegates and property delegates

This commit is contained in:
Edwin Jakobs
2023-03-18 20:32:43 +01:00
parent bab525cd92
commit 84e623c3e8
20 changed files with 553 additions and 143 deletions

View File

@@ -4,109 +4,22 @@ 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
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 {}
class FileWatcher(private val program: Program, val file: File, private val onChange: (File) -> Unit) {
val path = file.absoluteFile.toPath()
val parent = path.parent
val key = pathKeys.getOrPut(parent) {
parent.register(
watchService, arrayOf(StandardWatchEventKinds.ENTRY_MODIFY),
SensitivityWatchEventModifier.HIGH
)
}
val watchers = mutableListOf<() -> Unit>()
init {
watchThread
watching.getOrPut(path) {
mutableListOf()
}.add(this)
keyPaths.getOrPut(key) { parent }
}
fun stop() {
watching[path]?.remove(this)
}
internal fun triggerChange() {
program.launch {
onChange(file)
watchers.forEach { it() }
}
}
}
private val watchers = mutableMapOf<() -> Any, FileWatcher>()
fun <T> watchFile(program: Program, file: File, transducer: (File) -> T): () -> T {
var result = transducer(file)
val watcher = FileWatcher(program, file) {
try {
result = transducer(file)
} catch (e: Throwable) {
logger.error(e) {
"""exception while transducing file"""
}
}
}
val function = {
result
}
@Suppress("UNCHECKED_CAST")
watchers[function as () -> Any] = watcher
return function
}
/**
* Stops the watcher
*/
fun <T> (() -> T).stop() {
@Suppress("UNCHECKED_CAST")
watchers[this as () -> Any]?.stop()
}
/**
* Triggers reload
*/
fun <T> (() -> T).triggerChange() {
@Suppress("UNCHECKED_CAST")
watchers[this as () -> Any]?.triggerChange()
}
/**
* add watcher to file watcher
*/
fun <T, R> (() -> T).watch(transducer: (T) -> R): () -> R {
var result = transducer(this())
@Suppress("USELESS_CAST")
watchers[this as () -> Any?]!!.watchers.add {
result = transducer(this())
}
return { result }
}
@JvmName("programWatchFile")
fun <T> Program.watchFile(file: File, transducer: (File) -> T): () -> T = watchFile(this, file, transducer)
private val watching = mutableMapOf<Path, MutableList<FileWatcher>>()
private val pathKeys = mutableMapOf<Path, WatchKey>()
private val keyPaths = mutableMapOf<WatchKey, Path>()
private val keyPaths = WeakHashMap<WatchKey, Path>()
private val waiting = mutableMapOf<Path, Job>()
private val watchService by lazy {
@@ -119,13 +32,13 @@ private val watchThread by lazy {
while (true) {
val key = watchService.take()
val path = keyPaths[key]
key.pollEvents().forEach {
val contextPath = it.context() as Path
val fullPath = path?.resolve(contextPath)
fullPath?.let {
waiting[fullPath]?.cancel()
waiting[fullPath] = GlobalScope.launch {
delay(100)
watching[fullPath]?.forEach { w ->
@@ -138,3 +51,73 @@ private val watchThread by lazy {
}
}
}
class FileWatcher(
val file: File,
private val fileChangedEvent: Event<File>,
requestStopEvent: Event<Unit>? = null
) {
val path = file.absoluteFile.toPath()
val parent = path.parent
val key = pathKeys.getOrPut(parent) {
parent.register(
watchService, arrayOf(StandardWatchEventKinds.ENTRY_MODIFY),
SensitivityWatchEventModifier.HIGH
)
}
init {
watchThread
watching.getOrPut(path) {
mutableListOf()
}.add(this)
keyPaths.getOrPut(key) { parent }
requestStopEvent?.listenOnce {
stop()
}
}
fun stop() {
synchronized(watching) {
logger.info { "stopping, watcher stop requested" }
watching[path]?.remove(this)
}
}
internal fun triggerChange() {
fileChangedEvent.trigger(file)
}
}
fun <T> watchFile(
file: File,
contentsChangedEvent: Event<T>? = null,
requestStopEvent: Event<Unit>? = null,
transducer: (File) -> T
): () -> T {
var result = transducer(file)
val fileChangedEvent = Event<File>()
val watcher = FileWatcher(file, fileChangedEvent, requestStopEvent)
fileChangedEvent.listen {
try {
result = transducer(file)
contentsChangedEvent?.trigger(result)
} catch (e: Throwable) {
logger.error(e) {
"""exception while transducing file"""
}
}
}
return {
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

@@ -0,0 +1,38 @@
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
class FileWatcherDelegate<T>(
program: Program,
file: File,
valueChangedEvent: Event<T>? = null,
requestStopEvent: Event<Unit>? = null,
transducer: (File) -> T
) {
val watchValue = watchFile(file, valueChangedEvent, requestStopEvent, transducer)
var value = watchValue()
init {
program.launch {
while (true) {
value = watchValue()
yield()
}
}
}
operator fun getValue(any: Any, property: KProperty<*>): T {
return value
}
}
fun <R> Program.watchingFile(
file: File,
valueChangedEvent: Event<R>? = null,
requestStopEvent: Event<Unit>? = null,
transducer: (File) -> R
) = FileWatcherDelegate(this, file, valueChangedEvent, requestStopEvent, transducer)