
commonMain.space.kscience.dataforge.data.ActiveDataTree.kt Maven / Gradle / Ivy
package space.kscience.dataforge.data
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
* A mutable [DataTree.Companion.active]. It
*/
public class ActiveDataTree(
override val dataType: KType,
) : DataTree, DataSetBuilder, ActiveDataSet {
private val mutex = Mutex()
private val treeItems = HashMap>()
override suspend fun items(): Map> = mutex.withLock {
treeItems.filter { !it.key.body.startsWith("@") }
}
private val _updates = MutableSharedFlow()
override val updates: Flow
get() = _updates
private suspend fun remove(token: NameToken) {
mutex.withLock {
if (treeItems.remove(token) != null) {
_updates.emit(token.asName())
}
}
}
override suspend fun remove(name: Name) {
if (name.isEmpty()) error("Can't remove the root node")
(getItem(name.cutLast()).tree as? ActiveDataTree)?.remove(name.lastOrNull()!!)
}
private suspend fun set(token: NameToken, data: Data) {
mutex.withLock {
treeItems[token] = DataTreeItem.Leaf(data)
}
}
private suspend fun getOrCreateNode(token: NameToken): ActiveDataTree =
(treeItems[token] as? DataTreeItem.Node)?.tree as? ActiveDataTree
?: ActiveDataTree(dataType).also {
mutex.withLock {
treeItems[token] = DataTreeItem.Node(it)
}
}
private suspend fun getOrCreateNode(name: Name): ActiveDataTree {
return when (name.length) {
0 -> this
1 -> getOrCreateNode(name.firstOrNull()!!)
else -> getOrCreateNode(name.firstOrNull()!!).getOrCreateNode(name.cutFirst())
}
}
override suspend fun emit(name: Name, data: Data?) {
if (data == null) {
remove(name)
} else {
when (name.length) {
0 -> error("Can't add data with empty name")
1 -> set(name.firstOrNull()!!, data)
2 -> getOrCreateNode(name.cutLast()).set(name.lastOrNull()!!, data)
}
}
_updates.emit(name)
}
/**
* Copy given data set and mirror its changes to this [ActiveDataTree] in [this@setAndObserve]. Returns an update [Job]
*/
public fun CoroutineScope.setAndObserve(name: Name, dataSet: DataSet): Job = launch {
emit(name, dataSet)
dataSet.updates.collect { nameInBranch ->
emit(name + nameInBranch, dataSet.getData(nameInBranch))
}
}
}
/**
* Create a dynamic tree. Initial data is placed synchronously. Updates are propagated via [updatesScope]
*/
@Suppress("FunctionName")
public suspend fun ActiveDataTree(
type: KType,
block: suspend ActiveDataTree.() -> Unit,
): ActiveDataTree {
val tree = ActiveDataTree(type)
tree.block()
return tree
}
@Suppress("FunctionName")
public suspend inline fun ActiveDataTree(
crossinline block: suspend ActiveDataTree.() -> Unit,
): ActiveDataTree = ActiveDataTree(typeOf()).apply { block() }
public suspend inline fun ActiveDataTree.emit(
name: Name,
noinline block: suspend ActiveDataTree.() -> Unit,
): Unit = emit(name, ActiveDataTree(typeOf(), block))
public suspend inline fun ActiveDataTree.emit(
name: String,
noinline block: suspend ActiveDataTree.() -> Unit,
): Unit = emit(Name.parse(name), ActiveDataTree(typeOf(), block))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy