MapOfSequences.kt
src/jvmMain/kotlin/afield/graphics/vulkan/util/MapOfSequences.kt · 171 lines
package afield.graphics.vulkan.util
Keyed collections of running sequences that a host steps once per frame.
MapOfSequences holds the Runloop's preRun/postRun hooks and a mesh's update map, and can record each hook in the lifecycle ledger; MapOfGenerators holds generators that take an input each step and yield a value, without ledger entries.
import afield.runtime.lifecycle.Generator
class MapOfSequences<T>(private val ledgerKind: String? = null)
Named sequences advanced one step per update — the Runloop's preRun/postRun hooks, a mesh's
update. Setting a key replaces whatever ran under it; a sequence that ends is dropped.
When ledgerKind is given, every set is an afield.runtime.lifecycle.Ownership in the lifecycle ledger, attributed
to the box executing at the time, so lifecycle-census can say whose hook is still on the Runloop.
It is afield.runtime.lifecycle.ReleasePolicy.KEYED: a stop leaves the hook running, the next set of the same key
replaces it, and only the box's retirement (delete, document close) takes it out.
setUnowned is for library code that removes its own hook.
class MapOfSequences<T>(private val ledgerKind: String? = null) {
private val lock = Any()
private val starts = mutableMapOf<T, Sequence<Any?>>()
private var ongoings = mutableMapOf<T, Iterator<Any?>>()
The sequence each started key came from, so isCurrent can compare by identity.
private val ongoingSources = mutableMapOf<T, Sequence<Any?>>()
fun update()
fun update() {
val current = synchronized(lock) {
ongoings.entries.map { it.key to it.value }
}
current.forEach { (key, iterator) ->
val stillCurrent = synchronized(lock) {
ongoings[key] === iterator
}
if (!stillCurrent) return@forEach
val keep = if (iterator.hasNext()) {
iterator.next()
iterator.hasNext()
} else {
false
}
synchronized(lock) {
if (ongoings[key] === iterator && !keep) {
ongoings.remove(key)
ongoingSources.remove(key)
}
}
}
synchronized(lock) {
starts.forEach { (t, u) ->
ongoings[t] = u.iterator()
ongoingSources[t] = u
}
starts.clear()
}
}
operator fun set(t: T, sequence: Sequence<Any?>)
Run sequence under t from the next update, replacing whatever ran there.
operator fun set(t: T, sequence: Sequence<Any?>) {
setUnowned(t, sequence)
claim(t, sequence)
}
fun setUnowned(t: T, sequence: Sequence<Any?>)
set without the ledger entry, for library code that installs a hook on its own schedule and removes it itself — a renderer's geometry hook, a spreadsheet poll — rather than on a box's behalf.
fun setUnowned(t: T, sequence: Sequence<Any?>) {
synchronized(lock) {
starts[t] = sequence
}
}
fun isCurrent(t: T, sequence: Sequence<Any?>): Boolean
Whether sequence is still what runs, or will run, under t.
fun isCurrent(t: T, sequence: Sequence<Any?>): Boolean = synchronized(lock) {
starts[t] === sequence || (t !in starts && ongoingSources[t] === sequence)
}
fun removeIfCurrent(t: T, sequence: Sequence<Any?>)
Remove t only if sequence is still what it holds.
fun removeIfCurrent(t: T, sequence: Sequence<Any?>) {
synchronized(lock) {
if (starts[t] === sequence) starts.remove(t)
if (ongoingSources[t] === sequence) {
ongoings.remove(t)
ongoingSources.remove(t)
}
}
}
fun remove(t: T)
fun remove(t: T) {
synchronized(lock) {
starts.remove(t)
ongoings.remove(t)
ongoingSources.remove(t)
}
}
private fun claim(t: T, sequence: Sequence<Any?>): afield.runtime.lifecycle.Ownership? {
val kind = ledgerKind ?: return null
return afield.runtime.lifecycle.LifecycleLedger.claim(
kind,
t.toString(),
slot = afield.runtime.lifecycle.LedgerSlot(this, t),
live = { isCurrent(t, sequence) },
release = { removeIfCurrent(t, sequence) },
)
}
}
class MapOfGenerators<O, T>
class MapOfGenerators<O, T> {
val starts = mutableMapOf<String, Generator<O, T>>()
var ongoings = mutableMapOf<String, Generator<O, T>>()
fun update(input: O): MutableList<T>
fun update(input: O): MutableList<T> {
val collected = mutableListOf<T>()
ongoings.values.retainAll {
val o = it.next(input)
if (o != null)
collected.add(o)
!it.isCompleted
}
starts.forEach { (t, u) ->
val o = u.start()
if (!u.isCompleted)
ongoings[t] = u
}
starts.clear()
return collected
}
fun updateNoCollect(input: O)
fun updateNoCollect(input: O) {
val ongoingIterator = ongoings.values.iterator()
while (ongoingIterator.hasNext()) {
val gen = ongoingIterator.next()
gen.next(input)
if (gen.isCompleted) {
ongoingIterator.remove()
}
}
val startIterator = starts.entries.iterator()
while (startIterator.hasNext()) {
val entry = startIterator.next()
val gen = entry.value
gen.start()
if (!gen.isCompleted) {
ongoings[entry.key] = gen
}
startIterator.remove()
}
}
operator fun set(t: String, g: Generator<O, T>)
operator fun set(t: String, g: Generator<O, T>) {
starts[t] = g
}
fun remove(t : String)
fun remove(t : String) {
starts.remove(t)
ongoings.remove(t)
}
}