scratch.kt
src/jvmMain/kotlin/afield/runtime/lifecycle/scratch.kt · 90 lines
@file:afield.runtime.completion.CompletionCategory("Internal")
package afield.runtime.lifecycle
import afield.data.collections.get
import afield.data.collections.invoke
import afield.data.collections.s
import afield.data.collections.times
import afield.document.graph.minusAssign
import afield.document.graph.named
import afield.document.graph.plusAssign
import afield.document.properties.compareTo
import afield.document.properties.prop
import afield.document.workspace.file
import afield.graphics.render.rangeTo
import afield.lang.stacksl.evolution.render
import afield.math.autodiff.compareTo
import afield.math.autodiff.div
import afield.math.autodiff.minus
import afield.math.autodiff.plus
import afield.math.autodiff.times
import afield.math.linalg.invoke
import afield.math.linalg.remAssign
import afield.math.linalg.set
import afield.math.linalg.times
import afield.math.units.div
import afield.math.units.minus
import afield.math.units.plus
import afield.math.units.times
import afield.audio.synth.*
import afield.graphics.shader.compute
import afield.math.linalg.f
import afield.document.graph.Box
import afield.document.graph.HasProperties
import afield.runtime.lifecycle.RunScope
import afield.document.properties.Delegation
import kotlin.reflect.KProperty
val Box.once
val Box.once
get() = Once(this)
class Once(val inside : Box)
Property-delegate namespace bound to one Box; normally reached as box.once.
class Once(val inside : Box) {
}
class Onceing<T>()
Returns a lazy initializer for the generated property named by property.
The box property is read first. When it is null, the supplied computation runs and any
non-null result is stored back on the box. null is therefore the empty sentinel, not a cached
value: a computation returning null can run again on the next call.
The computation runs with the box's parent afield.runtime.lifecycle.RunScope current
(afield.runtime.lifecycle.BoxRunScopes.parent), not the run's, so what it registers — a slider callback, a render
module, an audio patch — belongs to the box rather than to the run that happened to compute it,
and the next run, which finds the cached value and does not compute again, does not release it.
Stop still releases what is running (an audio patch, a feeder, a process), and retiring the box
releases everything; either way, once something the computation registered is released, the
cached value is cleared, so the next run computes a fresh one rather than returning a dead one. A
value whose computation registered nothing — a shader string, most prop values — is never
cleared this way. Only registrations made on the calling thread are seen.
@Suppress("UNCHECKED_CAST")
class Onceing<T>() {
operator fun getValue(f: Once, property: KProperty<*>): ( () -> T) -> T {
operator fun getValue(f: Once, property: KProperty<*>): ( () -> T) -> T {
return { compute ->
val box = f.inside
var a = box.getPropertyRaw(property.name) as T?
if (a==null) {
val (value, registered) = RunScope.withCurrent(box.runScopes.parent) {
RunScope.collectingOwned { compute() }
}
a = value
if (a!=null) {
box.setPropertyRaw(property.name, a, true)
if (registered.isNotEmpty()) {
val cached: Any = a
val forget = {
if (box.getPropertyRaw(property.name) === cached) box.removePropertyRaw(property.name, true)
}
registered.forEach { it.onRelease(forget) }
}
}
}
a as T
}
}
}