util_lifecycle.kt
src/jvmMain/kotlin/afield/runtime/lifecycle/util_lifecycle.kt · 165 lines
package afield.runtime.lifecycle
import afield.data.table.div
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.document.graph.Box
import afield.runtime.execution.plus
import afield.runtime.kotlin.cache
import afield.audio.synth.*
import afield.data.table.get
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.app.settings.PersistOnExit.needingPersistance
import afield.document.persistence.SavingContext.Companion.format
import afield.app.diagnostics.logging.lprintln
import afield.data.collections.invoke
import afield.math.linalg.invoke
import java.io.File
import java.lang.reflect.*
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.concurrent.thread
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
LazyVar, a lazy value that can also be assigned, and its lazyVar factory.
Assigning the value before the first read means its initializer never runs, and LazyVar.discard
forgets the value so the next read builds it again. A value that is dropped without being
cached goes to an onDiscard callback, so something that needs releasing is released once.
class LazyVar<T> private constructor
A var-friendly lazy: if never explicitly set, the first get() runs initializer and caches it.
If explicitly set via the setter, initializer will not run.
discard forgets the value, and never waits for an initializer that is running on another thread.
That is the point of the class rather than a detail: its one user is Box.ki, whose initializer
builds a K2 engine (about a second on a cold JVM, under the SYNCHRONIZED lock), and whose discard
runs on a Ktor IO thread when a document's last client leaves. A value that would otherwise be
dropped uncached goes to onDiscard instead: the cached value a discard forgets, an initializer's
result when a discard ran while it was being built, and an initializer's result that lost to the
setter. So a value that needs releasing is released exactly once, by whichever thread drops it.
class LazyVar<T> private constructor(
private val initializer: () -> T,
private val mode: LazyThreadSafetyMode,
private val onDiscard: ((T) -> Unit)?
) : ReadWriteProperty<Any?, T> {
private object UNSET
private val value = AtomicReference<Any?>(UNSET)
private val discards = AtomicInteger()
For SYNCHRONIZED mode
private val lock = Any()
override fun getValue(thisRef: Any?, property: KProperty<*>): T
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val v1 = value.get()
if (v1 !== UNSET) {
@Suppress("UNCHECKED_CAST")
return v1 as T
}
return when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> synchronized(lock) {
val v2 = value.get()
@Suppress("UNCHECKED_CAST")
if (v2 !== UNSET) v2 as T else build()
}
// PUBLICATION: concurrent builders may all run; the first to publish wins.
else -> build()
}
}
Run the initializer and cache its result, unless it has been overtaken while it ran.
If the setter published first (or, for PUBLICATION, another builder did), the result goes to
onDiscard and the caller gets the published value. If discard ran, the result goes to
onDiscard and is still returned: this caller asked before the discard, and gets what it
would have had if it had asked a moment earlier.
The check comes after publishing so it cannot race discard, which bumps discards before
taking the value: either that take finds the result and releases it, or this check sees the
bump and does. The compare-and-sets make sure only one of them does.
private fun build(): T {
val generation = discards.get()
val built = initializer()
if (!value.compareAndSet(UNSET, built)) {
val published = value.get()
onDiscard?.invoke(built)
@Suppress("UNCHECKED_CAST")
return if (published !== UNSET) published as T else built
}
if (discards.get() != generation && value.compareAndSet(built, UNSET)) onDiscard?.invoke(built)
return built
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
Never waits for an initializer in flight: a value set now wins over the one it is building.
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value.set(value)
}
fun discard(): Boolean
Forget the value, so the next get() runs the initializer again, without waiting for an
initializer running on another thread. The cached value, if there was one, goes to
onDiscard here, on the calling thread. An initializer already running hands its result to
onDiscard when it returns, on its own thread, and does not cache it.
@return whether a cached value was discarded (an initializer in flight does not count).
fun discard(): Boolean {
discards.incrementAndGet()
val previous = value.getAndSet(UNSET)
if (previous === UNSET) return false
@Suppress("UNCHECKED_CAST")
onDiscard?.invoke(previous as T)
return true
}
val generation: Int get() = discards.get()
How many times discard has run. An initializer that reads this when it starts can tell later
whether a discard has overtaken it, before its result is even returned (see Box.buildKi).
val isInitialized: Boolean get() = value.get() !== UNSET
Returns true iff a value (either set or computed) is currently cached.
fun valueOrNull(): T?
The cached value, or null without running the initializer.
fun valueOrNull(): T? {
val v = value.get()
@Suppress("UNCHECKED_CAST")
return if (v === UNSET) null else v as T
}
companion object
fun <T> of
fun <T> of(
mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED,
onDiscard: ((T) -> Unit)? = null,
initializer: () -> T
): LazyVar<T> = LazyVar(initializer, mode, onDiscard)
}
}
fun <T> lazyVar
Convenience factory mirroring lazy { }; see LazyVar for onDiscard.
fun <T> lazyVar(
mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED,
onDiscard: ((T) -> Unit)? = null,
initializer: () -> T
): LazyVar<T> = LazyVar.of(mode, onDiscard, initializer)