Fix implementation of MutableDataTree.

All tests pass
This commit is contained in:
Alexander Nozik 2025-01-02 10:09:00 +03:00
parent 6634ece349
commit 531f95d55f
13 changed files with 162 additions and 121 deletions
dataforge-data/src
commonMain/kotlin/space/kscience/dataforge/data
commonTest/kotlin/space/kscience/dataforge/data
jvmTest/kotlin/space/kscience/dataforge/data
dataforge-workspace/src
commonMain/kotlin/space/kscience/dataforge/workspace
jvmMain/kotlin/space/kscience/dataforge/workspace
jvmTest/kotlin/space/kscience/dataforge/workspace

@ -8,6 +8,9 @@ import space.kscience.dataforge.names.*
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
/**
* A marker scope for data builders
*/
public interface DataBuilderScope<in T> { public interface DataBuilderScope<in T> {
public companion object : DataBuilderScope<Nothing> public companion object : DataBuilderScope<Nothing>
} }
@ -30,21 +33,19 @@ public fun interface DataSink<in T> : DataBuilderScope<T> {
* A mutable version of [DataTree] * A mutable version of [DataTree]
*/ */
public interface MutableDataTree<T> : DataTree<T>, DataSink<T> { public interface MutableDataTree<T> : DataTree<T>, DataSink<T> {
override var data: Data<T>?
override val items: Map<NameToken, MutableDataTree<T>> override val items: Map<NameToken, MutableDataTree<T>>
//
public fun getOrCreateItem(token: NameToken): MutableDataTree<T> // public fun getOrCreateItem(token: NameToken): MutableDataTree<T>
//
public suspend fun put(token: NameToken, data: Data<T>?) // public suspend fun put(token: NameToken, data: Data<T>?)
//
override suspend fun put(name: Name, data: Data<T>?): Unit { // override suspend fun put(name: Name, data: Data<T>?): Unit {
when (name.length) { // when (name.length) {
0 -> this.data = data // 0 -> this.data = data
1 -> put(name.first(), data) // 1 -> put(name.first(), data)
else -> getOrCreateItem(name.first()).put(name.cutFirst(), data) // else -> getOrCreateItem(name.first()).put(name.cutFirst(), data)
} // }
} // }
} }
/** /**
@ -62,11 +63,12 @@ private class MutableDataTreeRoot<T>(
) : MutableDataTree<T> { ) : MutableDataTree<T> {
override val items = HashMap<NameToken, MutableDataTree<T>>() override val items = HashMap<NameToken, MutableDataTree<T>>()
override val updates = MutableSharedFlow<Name>(extraBufferCapacity = 100) override val updates = MutableSharedFlow<Name>()
inner class MutableDataTreeBranch(val branchName: Name) : MutableDataTree<T> { inner class MutableDataTreeBranch(val branchName: Name) : MutableDataTree<T> {
override var data: Data<T>? = null override var data: Data<T>? = null
private set
override val items = HashMap<NameToken, MutableDataTree<T>>() override val items = HashMap<NameToken, MutableDataTree<T>>()
@ -75,26 +77,43 @@ private class MutableDataTreeRoot<T>(
} }
override val dataType: KType get() = this@MutableDataTreeRoot.dataType override val dataType: KType get() = this@MutableDataTreeRoot.dataType
override suspend fun put(
name: Name,
data: Data<T>?
) {
when (name.length) {
0 -> {
this.data = data
this@MutableDataTreeRoot.updates.emit(branchName)
}
override fun getOrCreateItem(token: NameToken): MutableDataTree<T> = else -> {
items.getOrPut(token) { MutableDataTreeBranch(branchName + token) } val token = name.first()
items.getOrPut(token) { MutableDataTreeBranch(branchName + token) }.put(name.cutFirst(), data)
}
}
}
}
override var data: Data<T>? = null
private set
override suspend fun put(token: NameToken, data: Data<T>?) { override suspend fun put(
this.data = data name: Name,
this@MutableDataTreeRoot.updates.emit(branchName + token) data: Data<T>?
) {
when (name.length) {
0 -> {
this.data = data
this@MutableDataTreeRoot.updates.emit(Name.EMPTY)
}
else -> {
val token = name.first()
items.getOrPut(token) { MutableDataTreeBranch(token.asName()) }.put(name.cutFirst(), data)
}
} }
} }
override var data: Data<T>? = null
override fun getOrCreateItem(token: NameToken): MutableDataTree<T> = items.getOrPut(token) {
MutableDataTreeBranch(token.asName())
}
override suspend fun put(token: NameToken, data: Data<T>?) {
this.data = data
updates.emit(token.asName())
}
} }
/** /**
@ -106,7 +125,7 @@ public fun <T> MutableDataTree(
): MutableDataTree<T> = MutableDataTreeRoot<T>(type) ): MutableDataTree<T> = MutableDataTreeRoot<T>(type)
/** /**
* Create and initialize a observable mutable data tree. * Create and initialize an observable mutable data tree.
*/ */
@OptIn(UnsafeKType::class) @OptIn(UnsafeKType::class)
public inline fun <reified T> MutableDataTree( public inline fun <reified T> MutableDataTree(

@ -38,9 +38,8 @@ public interface ObservableDataSource<out T> : DataSource<T> {
public val updates: Flow<Name> public val updates: Flow<Name>
} }
public suspend fun <T> ObservableDataSource<T>.awaitData(name: Name): Data<T> { public suspend fun <T> ObservableDataSource<T>.awaitData(name: Name): Data<T> =
return read(name) ?: updates.filter { it == name }.map { read(name) }.filterNotNull().first() read(name) ?: updates.filter { it == name }.mapNotNull { read(name) }.first()
}
public suspend fun <T> ObservableDataSource<T>.awaitData(name: String): Data<T> = public suspend fun <T> ObservableDataSource<T>.awaitData(name: String): Data<T> =
awaitData(name.parseAsName()) awaitData(name.parseAsName())

@ -11,13 +11,13 @@ import kotlin.reflect.typeOf
public fun interface StaticDataBuilder<T> : DataBuilderScope<T> { public fun interface StaticDataBuilder<T> : DataBuilderScope<T> {
public fun put(name: Name, data: Data<T>) public fun data(name: Name, data: Data<T>)
} }
private class DataMapBuilder<T> : StaticDataBuilder<T> { private class DataMapBuilder<T> : StaticDataBuilder<T> {
val map = mutableMapOf<Name, Data<T>>() val map = mutableMapOf<Name, Data<T>>()
override fun put(name: Name, data: Data<T>) { override fun data(name: Name, data: Data<T>) {
if (map.containsKey(name)) { if (map.containsKey(name)) {
error("Duplicate key '$name'") error("Duplicate key '$name'")
} else { } else {
@ -26,31 +26,31 @@ private class DataMapBuilder<T> : StaticDataBuilder<T> {
} }
} }
public fun <T> StaticDataBuilder<T>.put(name: String, data: Data<T>) { public fun <T> StaticDataBuilder<T>.data(name: String, data: Data<T>) {
put(name.parseAsName(), data) data(name.parseAsName(), data)
} }
public inline fun <T, reified T1 : T> StaticDataBuilder<T>.putValue( public inline fun <T, reified T1 : T> StaticDataBuilder<T>.value(
name: String, name: String,
value: T1, value: T1,
metaBuilder: MutableMeta.() -> Unit = {} metaBuilder: MutableMeta.() -> Unit = {}
) { ) {
put(name, Data(value, Meta(metaBuilder))) data(name, Data(value, Meta(metaBuilder)))
} }
public fun <T> StaticDataBuilder<T>.putAll(prefix: Name, block: StaticDataBuilder<T>.() -> Unit) { public fun <T> StaticDataBuilder<T>.node(prefix: Name, block: StaticDataBuilder<T>.() -> Unit) {
val map = DataMapBuilder<T>().apply(block).map val map = DataMapBuilder<T>().apply(block).map
map.forEach { (name, data) -> map.forEach { (name, data) ->
put(prefix + name, data) data(prefix + name, data)
} }
} }
public fun <T> StaticDataBuilder<T>.putAll(prefix: String, block: StaticDataBuilder<T>.() -> Unit) = public fun <T> StaticDataBuilder<T>.node(prefix: String, block: StaticDataBuilder<T>.() -> Unit) =
putAll(prefix.parseAsName(), block) node(prefix.parseAsName(), block)
public fun <T> StaticDataBuilder<T>.putAll(prefix: String, tree: DataTree<T>) { public fun <T> StaticDataBuilder<T>.node(prefix: String, tree: DataTree<T>) {
tree.forEach { data -> tree.forEach { data ->
put(prefix + data.name, data) data(prefix.parseAsName() + data.name, data)
} }
} }

@ -13,12 +13,12 @@ internal class DataTreeBuilderTest {
@Test @Test
fun testTreeBuild() = runTest(timeout = 500.milliseconds) { fun testTreeBuild() = runTest(timeout = 500.milliseconds) {
val node = DataTree.static<Any> { val node = DataTree.static<Any> {
putAll("primary") { node("primary") {
putValue("a", "a") value("a", "a")
putValue("b", "b") value("b", "b")
} }
putValue("c.d", "c.d") value("c.d", "c.d")
putValue("c.f", "c.f") value("c.f", "c.f")
} }
assertEquals("a", node["primary.a"]?.await()) assertEquals("a", node["primary.a"]?.await())
assertEquals("b", node["primary.b"]?.await()) assertEquals("b", node["primary.b"]?.await())
@ -30,17 +30,17 @@ internal class DataTreeBuilderTest {
@Test @Test
fun testDataUpdate() = runTest(timeout = 500.milliseconds) { fun testDataUpdate() = runTest(timeout = 500.milliseconds) {
val updateData = DataTree.static<Any> { val updateData = DataTree.static<Any> {
put("a", Data.wrapValue("a")) data("a", Data.wrapValue("a"))
put("b", Data.wrapValue("b")) data("b", Data.wrapValue("b"))
} }
val node = DataTree.static<Any> { val node = DataTree.static<Any> {
putAll("primary") { node("primary") {
putValue("a", "a") value("a", "a")
putValue("b", "b") value("b", "b")
} }
putValue("root", "root") value("root", "root")
putAll("update", updateData) node("update", updateData)
} }
assertEquals("a", node["update.a"]?.await()) assertEquals("a", node["update.a"]?.await())
@ -54,7 +54,9 @@ internal class DataTreeBuilderTest {
val subNode = MutableDataTree<Int>() val subNode = MutableDataTree<Int>()
val rootNode = MutableDataTree<Int>() { val rootNode = MutableDataTree<Int>() {
job = launch { putAllAndWatch(subNode, "sub".asName()) } job = launch {
putAllAndWatch(subNode, "sub".asName())
}
} }
repeat(10) { repeat(10) {

@ -1,7 +1,6 @@
package space.kscience.dataforge.data package space.kscience.dataforge.data
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.actions.Action import space.kscience.dataforge.actions.Action
import space.kscience.dataforge.actions.invoke import space.kscience.dataforge.actions.invoke
@ -21,14 +20,13 @@ internal class ActionsTest {
val data: DataTree<Int> = DataTree.static { val data: DataTree<Int> = DataTree.static {
repeat(10) { repeat(10) {
putValue(it.toString(), it) value(it.toString(), it)
} }
} }
val result = plusOne(data) val result = plusOne(data)
advanceUntilIdle() assertEquals(2, result.awaitData("1").await())
assertEquals(2, result["1"]?.await())
} }
@Test @Test

@ -62,7 +62,7 @@ public interface TaskWithSpec<T, C : Any> : Task<T> {
// block: C.() -> Unit = {}, // block: C.() -> Unit = {},
//): TaskResult<T> = execute(workspace, taskName, spec(block)) //): TaskResult<T> = execute(workspace, taskName, spec(block))
public class TaskResultScope<T>( public class TaskResultScope<in T>(
public val resultType: KType, public val resultType: KType,
public val workspace: Workspace, public val workspace: Workspace,
public val taskName: Name, public val taskName: Name,

@ -1,14 +1,13 @@
package space.kscience.dataforge.workspace package space.kscience.dataforge.workspace
import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.*
import space.kscience.dataforge.data.NamedValueWithMeta
import space.kscience.dataforge.data.transformEach
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.copy import space.kscience.dataforge.meta.copy
import space.kscience.dataforge.meta.remove import space.kscience.dataforge.meta.remove
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.UnsafeKType
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
@ -77,13 +76,15 @@ public val TaskResultScope<*>.allData: DataSelector<*>
* @param dataMetaTransform additional transformation of individual data meta. * @param dataMetaTransform additional transformation of individual data meta.
* @param action process individual data asynchronously. * @param action process individual data asynchronously.
*/ */
@OptIn(UnsafeKType::class)
@DFExperimental @DFExperimental
public suspend inline fun <T, reified R> TaskResultScope<R>.transformEach( public suspend fun <T, R> TaskResultScope<R>.transformEach(
selector: DataSelector<T>, selector: DataSelector<T>,
dependencyMeta: Meta = defaultDependencyMeta, dependencyMeta: Meta = defaultDependencyMeta,
crossinline dataMetaTransform: MutableMeta.(name: Name) -> Unit = {}, dataMetaTransform: MutableMeta.(name: Name) -> Unit = {},
crossinline action: suspend (NamedValueWithMeta<T>) -> R, action: suspend NamedValueWithMeta<T>.() -> R,
): DataTree<R> = from(selector, dependencyMeta).transformEach<T, R>( ): DataTree<R> = from(selector, dependencyMeta).transformEach<T, R>(
resultType,
workspace.context, workspace.context,
metaTransform = { name -> metaTransform = { name ->
taskMeta[taskName]?.let { taskName put it } taskMeta[taskName]?.let { taskName put it }
@ -93,6 +94,15 @@ public suspend inline fun <T, reified R> TaskResultScope<R>.transformEach(
action(it) action(it)
} }
@OptIn(UnsafeKType::class)
public fun <R> TaskResultScope<R>.result(data: Data<R>): DataTree<R> = DataTree.static(resultType) {
data(Name.EMPTY, data)
}
@OptIn(UnsafeKType::class)
public fun <R> TaskResultScope<R>.result(builder: StaticDataBuilder<R>.() -> Unit): DataTree<R> =
DataTree.static(resultType, builder)
///** ///**
// * Set given [dataSet] as a task result. // * Set given [dataSet] as a task result.
// */ // */

@ -68,10 +68,18 @@ public class FileDataTree(
} }
path.isDirectory() -> { path.isDirectory() -> {
val dataBinary: Binary? = path.resolve(IOPlugin.DATA_FILE_NAME)?.asBinary() //FIXME find data and meta in a single pass instead of two
val meta: Meta? = path.find { it.fileName.startsWith(IOPlugin.META_FILE_NAME) }?.let {
val dataBinary: Binary? = path.listDirectoryEntries().find {
it.fileName.nameWithoutExtension == IOPlugin.DATA_FILE_NAME
}?.asBinary()
val meta: Meta? = path.listDirectoryEntries().find {
it.fileName.nameWithoutExtension == IOPlugin.META_FILE_NAME
}?.let {
io.readMetaFileOrNull(it) io.readMetaFileOrNull(it)
} }
if (dataBinary != null || meta != null) { if (dataBinary != null || meta != null) {
StaticData( StaticData(
typeOf<Binary>(), typeOf<Binary>(),
@ -156,6 +164,9 @@ public class FileDataTree(
} }
} }
public fun IOPlugin.readDirectory(path: Path, monitor: Boolean = false): FileDataTree =
FileDataTree(this, path, monitor)
///** ///**
// * @param resources The names of the resources to read. // * @param resources The names of the resources to read.

@ -3,7 +3,7 @@ package space.kscience.dataforge.workspace
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import space.kscience.dataforge.data.putValue import space.kscience.dataforge.data.value
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
@ -22,14 +22,14 @@ internal class CachingWorkspaceTest {
data { data {
//statically initialize data //statically initialize data
repeat(5) { repeat(5) {
putValue("myData[$it]", it) value("myData[$it]", it)
} }
} }
inMemoryCache() inMemoryCache()
val doFirst by task<Any> { val doFirst by task<Any> {
transformEach(allData) { (name, _, _) -> transformEach(allData) {
firstCounter++ firstCounter++
println("Done first on $name with flag=${taskMeta["flag"].boolean}") println("Done first on $name with flag=${taskMeta["flag"].boolean}")
} }
@ -39,7 +39,7 @@ internal class CachingWorkspaceTest {
transformEach( transformEach(
doFirst, doFirst,
dependencyMeta = if (taskMeta["flag"].boolean == true) taskMeta else Meta.EMPTY dependencyMeta = if (taskMeta["flag"].boolean == true) taskMeta else Meta.EMPTY
) { (name, _, _) -> ) {
secondCounter++ secondCounter++
println("Done second on $name with flag=${taskMeta["flag"].boolean ?: false}") println("Done second on $name with flag=${taskMeta["flag"].boolean ?: false}")
} }

@ -20,14 +20,12 @@ class DataPropagationTestPlugin : WorkspacePlugin() {
val result: Data<Int> = selectedData.foldToData(0) { result, data -> val result: Data<Int> = selectedData.foldToData(0) { result, data ->
result + data.value result + data.value
} }
put("result", result) result(result)
} }
val singleData by task<Int> { val singleData by task<Int> {
workspace.data.filterByType<Int>()["myData[12]"]?.let { result(workspace.data.filterByType<Int>()["myData[12]"]!!)
put("result", it)
}
} }
@ -47,7 +45,7 @@ class DataPropagationTest {
} }
data { data {
repeat(100) { repeat(100) {
putValue("myData[$it]", it) value("myData[$it]", it)
} }
} }
} }
@ -55,12 +53,12 @@ class DataPropagationTest {
@Test @Test
fun testAllData() = runTest { fun testAllData() = runTest {
val node = testWorkspace.produce("Test.allData") val node = testWorkspace.produce("Test.allData")
assertEquals(4950, node.content.asSequence().single().await()) assertEquals(4950, node.content.data?.await())
} }
@Test @Test
fun testSingleData() = runTest { fun testSingleData() = runTest {
val node = testWorkspace.produce("Test.singleData") val node = testWorkspace.produce("Test.singleData")
assertEquals(12, node.content.asSequence().single().await()) assertEquals(12, node.content.data?.await())
} }
} }

@ -12,7 +12,6 @@ import space.kscience.dataforge.io.*
import space.kscience.dataforge.io.yaml.YamlPlugin import space.kscience.dataforge.io.yaml.YamlPlugin
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import java.nio.file.Files import java.nio.file.Files
import kotlin.io.path.deleteExisting import kotlin.io.path.deleteExisting
import kotlin.io.path.fileSize import kotlin.io.path.fileSize
@ -22,13 +21,13 @@ import kotlin.test.assertEquals
class FileDataTest { class FileDataTest {
val dataNode = DataTree<String> { val dataNode = DataTree.static<String> {
putAll("dir") { node("dir") {
putValue("a", "Some string") { value("a", "Some string") {
"content" put "Some string" "content" put "Some string"
} }
} }
putValue("b", "root data") value("b", "root data")
// meta { // meta {
// "content" put "This is root meta node" // "content" put "This is root meta node"
// } // }
@ -51,10 +50,10 @@ class FileDataTest {
val dir = Files.createTempDirectory("df_data_node") val dir = Files.createTempDirectory("df_data_node")
io.writeDataDirectory(dir, dataNode, StringIOFormat) io.writeDataDirectory(dir, dataNode, StringIOFormat)
println(dir.toUri().toString()) println(dir.toUri().toString())
val data = DataTree { val data = io.readDirectory(dir)
io.readAsDataTree(Name.EMPTY, dir) val reconstructed = data.transformEach(this) { (_, value) ->
value.toByteArray().decodeToString()
} }
val reconstructed = data.map { (_, value) -> value.toByteArray().decodeToString() }
assertEquals(dataNode["dir.a"]?.meta?.get("content"), reconstructed["dir.a"]?.meta?.get("content")) assertEquals(dataNode["dir.a"]?.meta?.get("content"), reconstructed["dir.a"]?.meta?.get("content"))
assertEquals(dataNode["b"]?.await(), reconstructed["b"]?.await()) assertEquals(dataNode["b"]?.await(), reconstructed["b"]?.await())
} }
@ -68,8 +67,9 @@ class FileDataTest {
zip.deleteExisting() zip.deleteExisting()
io.writeZip(zip, dataNode, StringIOFormat) io.writeZip(zip, dataNode, StringIOFormat)
println(zip.toUri().toString()) println(zip.toUri().toString())
val reconstructed = DataTree { io.readAsDataTree(Name.EMPTY, zip) } val reconstructed = io.readDirectory(zip).transformEach(this) { (_, value) ->
.map { (_, value) -> value.toByteArray().decodeToString() } value.toByteArray().decodeToString()
}
assertEquals(dataNode["dir.a"]?.meta?.get("content"), reconstructed["dir.a"]?.meta?.get("content")) assertEquals(dataNode["dir.a"]?.meta?.get("content"), reconstructed["dir.a"]?.meta?.get("content"))
assertEquals(dataNode["b"]?.await(), reconstructed["b"]?.await()) assertEquals(dataNode["b"]?.await(), reconstructed["b"]?.await())

@ -3,7 +3,7 @@ package space.kscience.dataforge.workspace
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import space.kscience.dataforge.data.putValue import space.kscience.dataforge.data.value
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import java.nio.file.Files import java.nio.file.Files
@ -16,13 +16,13 @@ class FileWorkspaceCacheTest {
data { data {
//statically initialize data //statically initialize data
repeat(5) { repeat(5) {
putValue("myData[$it]", it) value("myData[$it]", it)
} }
} }
fileCache(Files.createTempDirectory("dataforge-temporary-cache")) fileCache(Files.createTempDirectory("dataforge-temporary-cache"))
val echo by task<String> { val echo by task<String> {
transformEach(dataByType<String>()) { arg, _, _ -> arg } transformEach(dataByType<String>()) { value }
} }
} }

@ -37,9 +37,9 @@ internal object TestPlugin : WorkspacePlugin() {
val test by task { val test by task {
// type is inferred // type is inferred
transformEach(dataByType<Int>()) { arg, _, _ -> transformEach(dataByType<Int>()) {
logger.info { "Test: $arg" } logger.info { "Test: $value" }
arg value
} }
} }
@ -62,42 +62,42 @@ internal class SimpleWorkspaceTest {
data { data {
//statically initialize data //statically initialize data
repeat(100) { repeat(100) {
putValue("myData[$it]", it) value("myData[$it]", it)
} }
} }
val filterOne by task<Int> { val filterOne by task<Int> {
val name by taskMeta.string { error("Name field not defined") } val name by taskMeta.string { error("Name field not defined") }
from(testPluginFactory) { test }[name]?.let { source: Data<Int> -> result(from(testPluginFactory) { test }[name]!!)
put(name, source)
}
} }
val square by task<Int> { val square by task<Int> {
transformEach(dataByType<Int>()) { arg, name, meta -> transformEach(dataByType<Int>()) {
if (meta["testFlag"].boolean == true) { if (meta["testFlag"].boolean == true) {
println("Side effect") println("Side effect")
} }
workspace.logger.info { "Starting square on $name" } workspace.logger.info { "Starting square on $name" }
arg * arg value * value
} }
} }
val linear by task<Int> { val linear by task<Int> {
transformEach(dataByType<Int>()) { arg, name, _ -> transformEach(dataByType<Int>()) {
workspace.logger.info { "Starting linear on $name" } workspace.logger.info { "Starting linear on $name" }
arg * 2 + 1 value * 2 + 1
} }
} }
val fullSquare by task<Int> { val fullSquare by task<Int> {
val squareData = from(square) val squareData = from(square)
val linearData = from(linear) val linearData = from(linear)
squareData.forEach { data -> result {
val newData: Data<Int> = data.combine(linearData[data.name]!!) { l, r -> squareData.forEach { data ->
l + r val newData: Data<Int> = data.combine(linearData[data.name]!!) { l, r ->
l + r
}
data(data.name, newData)
} }
put(data.name, newData)
} }
} }
@ -106,7 +106,7 @@ internal class SimpleWorkspaceTest {
val res = from(square).foldToData(0) { l, r -> val res = from(square).foldToData(0) { l, r ->
l + r.value l + r.value
} }
put("sum", res) result(res)
} }
val averageByGroup by task<Int> { val averageByGroup by task<Int> {
@ -116,13 +116,15 @@ internal class SimpleWorkspaceTest {
l + r.value l + r.value
} }
put("even", evenSum)
val oddSum = workspace.data.filterByType<Int> { name, _, _ -> val oddSum = workspace.data.filterByType<Int> { name, _, _ ->
name.toString().toInt() % 2 == 1 name.toString().toInt() % 2 == 1
}.foldToData(0) { l, r -> }.foldToData(0) { l, r ->
l + r.value l + r.value
} }
put("odd", oddSum) result {
data("even", evenSum)
data("odd", oddSum)
}
} }
val delta by task<Int> { val delta by task<Int> {
@ -132,15 +134,17 @@ internal class SimpleWorkspaceTest {
val res = even.combine(odd) { l, r -> val res = even.combine(odd) { l, r ->
l - r l - r
} }
put("res", res) result(res)
} }
val customPipe by task<Int> { val customPipe by task<Int> {
workspace.data.filterByType<Int>().forEach { data -> result {
val meta = data.meta.toMutableMeta().apply { workspace.data.filterByType<Int>().forEach { data ->
"newValue" put 22 val meta = data.meta.toMutableMeta().apply {
"newValue" put 22
}
data(data.name + "new", data.transform { (data.meta["value"].int ?: 0) + it })
} }
put(data.name + "new", data.transform { (data.meta["value"].int ?: 0) + it })
} }
} }
@ -157,7 +161,7 @@ internal class SimpleWorkspaceTest {
@Test @Test
fun testMetaPropagation() = runTest(timeout = 100.milliseconds) { fun testMetaPropagation() = runTest(timeout = 100.milliseconds) {
val node = workspace.produce("sum") { "testFlag" put true } val node = workspace.produce("sum") { "testFlag" put true }
val res = node["sum"]!!.await() val res = node.data?.await()
} }
@Test @Test
@ -175,7 +179,7 @@ internal class SimpleWorkspaceTest {
""" """
Name: ${it.name} Name: ${it.name}
Meta: ${it.meta} Meta: ${it.meta}
Data: ${it.data.await()} Data: ${it.await()}
""".trimIndent() """.trimIndent()
) )
} }