All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.space.kscience.dataforge.data.ActiveDataTree.kt Maven / Gradle / Ivy

There is a newer version: 0.7.0
Show newest version
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