, Meta> = dependencies
/**
* Register plugin dependency and return a delegate which provides lazily initialized reference to dependent plugin
*/
- protected fun require(factory: PluginFactory
): ReadOnlyProperty {
- dependencies.add(factory)
+ protected fun require(factory: PluginFactory
, meta: Meta = Meta.EMPTY): ReadOnlyProperty {
+ dependencies[factory] = meta
return PluginDependencyDelegate(factory.type)
}
}
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
index 605e6f8b..b701d3d6 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
@@ -3,15 +3,13 @@ package hep.dataforge.context
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr
-import hep.dataforge.meta.sequence
+import hep.dataforge.meta.itemSequence
+import hep.dataforge.misc.Named
import hep.dataforge.names.Name
-import hep.dataforge.names.plus
import hep.dataforge.provider.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
-import mu.KLogger
-import mu.KotlinLogging
import kotlin.coroutines.CoroutineContext
/**
@@ -24,11 +22,10 @@ import kotlin.coroutines.CoroutineContext
* be overridden by plugin implementation.
*
*/
-public open class Context(
+public open class Context internal constructor(
final override val name: Name,
public val parent: Context?,
meta: Meta,
- plugins: Set = emptySet(),
) : Named, MetaRepr, Provider, CoroutineScope {
/**
@@ -40,28 +37,23 @@ public open class Context(
Laminate(meta, parent.properties)
}
- /**
- * Context logger
- */
- public val logger: KLogger = KotlinLogging.logger(name.toString())
-
/**
* A [PluginManager] for current context
*/
- public val plugins: PluginManager by lazy { PluginManager(this, plugins)}
+ public val plugins: PluginManager by lazy { PluginManager(this) }
override val defaultTarget: String get() = Plugin.TARGET
public fun content(target: String, inherit: Boolean): Map {
return if (inherit) {
when (target) {
- PROPERTY_TARGET -> properties.sequence().toMap()
+ PROPERTY_TARGET -> properties.itemSequence().toMap()
Plugin.TARGET -> plugins.list(true).associateBy { it.name }
else -> emptyMap()
}
} else {
when (target) {
- PROPERTY_TARGET -> properties.layers.firstOrNull()?.sequence()?.toMap() ?: emptyMap()
+ PROPERTY_TARGET -> properties.layers.firstOrNull()?.itemSequence()?.toMap() ?: emptyMap()
Plugin.TARGET -> plugins.list(false).associateBy { it.name }
else -> emptyMap()
}
@@ -95,6 +87,9 @@ public open class Context(
}
}
+public fun Context(name: String, parent: Context = Global, block: ContextBuilder.() -> Unit = {}): Context =
+ Global.context(name, parent, block)
+
/**
* The interface for something that encapsulated in context
*
@@ -106,12 +101,4 @@ public interface ContextAware {
* @return
*/
public val context: Context
-
- public val logger: KLogger
- get() = if (this is Named) {
- KotlinLogging.logger((context.name + this.name).toString())
- } else {
- context.logger
- }
-
}
\ No newline at end of file
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
index b742474c..f124d3d2 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
@@ -1,24 +1,29 @@
package hep.dataforge.context
-import hep.dataforge.meta.*
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBuilder
+import hep.dataforge.meta.seal
+import hep.dataforge.misc.DFBuilder
+import hep.dataforge.misc.DFExperimental
import hep.dataforge.names.toName
+import kotlin.collections.HashMap
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.forEach
+import kotlin.collections.set
/**
* A convenience builder for context
*/
@DFBuilder
public class ContextBuilder(private val parent: Context = Global, public var name: String = "@anonymous") {
- private val plugins = HashSet()
+ private val factories = HashMap, Meta>()
private var meta = MetaBuilder()
public fun properties(action: MetaBuilder.() -> Unit) {
meta.action()
}
- public fun plugin(plugin: Plugin) {
- plugins.add(plugin)
- }
-
@OptIn(DFExperimental::class)
private fun findPluginFactory(tag: PluginTag): PluginFactory<*> =
parent.gatherInSequence>(PluginFactory.TYPE).values
@@ -26,12 +31,11 @@ public class ContextBuilder(private val parent: Context = Global, public var nam
public fun plugin(tag: PluginTag, metaBuilder: MetaBuilder.() -> Unit = {}) {
val factory = findPluginFactory(tag)
- val plugin = factory.invoke(Meta(metaBuilder), parent)
- plugins.add(plugin)
+ factories[factory] = Meta(metaBuilder)
}
- public fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) {
- plugins.add(builder.invoke(Meta(action)))
+ public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) {
+ factories[factory] = Meta(metaBuilder)
}
public fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
@@ -39,6 +43,10 @@ public class ContextBuilder(private val parent: Context = Global, public var nam
}
public fun build(): Context {
- return Context(name.toName(), parent, meta.seal(), plugins)
+ return Context(name.toName(), parent, meta.seal()).apply {
+ factories.forEach { (factory, meta) ->
+ plugins.load(factory, meta)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
index ff0e3a0c..3d4aea3d 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
@@ -3,10 +3,11 @@ package hep.dataforge.context
import hep.dataforge.context.Plugin.Companion.TARGET
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr
+import hep.dataforge.misc.Named
+import hep.dataforge.misc.Type
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.provider.Provider
-import hep.dataforge.provider.Type
/**
* The interface to define a Context plugin. A plugin stores all runtime features of a context.
@@ -37,7 +38,7 @@ public interface Plugin : Named, ContextAware, Provider, MetaRepr {
* dependencies must be initialized and enabled in the Context before this
* plugin is enabled.
*/
- public fun dependsOn(): Collection>
+ public fun dependsOn(): Map, Meta>
/**
* Start this plugin and attach registration info to the context. This method
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt
new file mode 100644
index 00000000..18e40fed
--- /dev/null
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt
@@ -0,0 +1,14 @@
+package hep.dataforge.context
+
+import hep.dataforge.misc.Type
+import kotlin.reflect.KClass
+
+@Type(PluginFactory.TYPE)
+public interface PluginFactory : Factory {
+ public val tag: PluginTag
+ public val type: KClass
+
+ public companion object {
+ public const val TYPE: String = "pluginFactory"
+ }
+}
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
index 3e9def85..4854b328 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
@@ -2,34 +2,23 @@ package hep.dataforge.context
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
-import hep.dataforge.provider.Type
import kotlin.reflect.KClass
-@Type(PluginFactory.TYPE)
-public interface PluginFactory : Factory {
- public val tag: PluginTag
- public val type: KClass
-
- public companion object {
- public const val TYPE: String = "pluginFactory"
- }
-}
-
/**
* The manager for plugin system. Should monitor plugin dependencies and locks.
*
* @property context A context for this plugin manager
* @author Alexander Nozik
*/
-public class PluginManager(override val context: Context, plugins: Set) : ContextAware, Iterable {
+public class PluginManager(override val context: Context) : ContextAware, Iterable {
//TODO refactor to read-only container
/**
* A set of loaded plugins
*/
- private val plugins: HashSet = HashSet(plugins)
+ private val plugins: HashSet = HashSet()
init {
plugins.forEach { it.attach(context) }
@@ -100,8 +89,8 @@ public class PluginManager(override val context: Context, plugins: Set)
if (get(plugin::class, plugin.tag, recursive = false) != null) {
error("Plugin with tag ${plugin.tag} already exists in ${context.name}")
} else {
- for (tag in plugin.dependsOn()) {
- fetch(tag, true)
+ for ((factory, meta) in plugin.dependsOn()) {
+ fetch(factory, meta, true)
}
logger.info { "Loading plugin ${plugin.name} into ${context.name}" }
@@ -134,7 +123,7 @@ public class PluginManager(override val context: Context, plugins: Set)
/**
* Get an existing plugin with given meta or load new one using provided factory
*/
- public fun fetch(factory: PluginFactory, recursive: Boolean = true, meta: Meta = Meta.EMPTY): T {
+ public fun fetch(factory: PluginFactory, meta: Meta = Meta.EMPTY, recursive: Boolean = true): T {
val loaded = get(factory.type, factory.tag, recursive)
return when {
loaded == null -> load(factory(meta, context))
@@ -147,7 +136,7 @@ public class PluginManager(override val context: Context, plugins: Set)
factory: PluginFactory,
recursive: Boolean = true,
metaBuilder: MetaBuilder.() -> Unit,
- ): T = fetch(factory, recursive, Meta(metaBuilder))
+ ): T = fetch(factory, Meta(metaBuilder), recursive)
override fun iterator(): Iterator = plugins.iterator()
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt
new file mode 100644
index 00000000..c43f7ea2
--- /dev/null
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt
@@ -0,0 +1,22 @@
+package hep.dataforge.context
+
+import hep.dataforge.misc.Named
+import hep.dataforge.provider.Path
+import mu.KLogger
+import mu.KotlinLogging
+
+/**
+ * The logger specific to this context
+ */
+public val Context.logger: KLogger get() = KotlinLogging.logger(name.toString())
+
+/**
+ * The logger
+ */
+public val ContextAware.logger: KLogger
+ get() = if (this is Named) {
+ KotlinLogging.logger(Path(context.name, this.name).toString())
+ } else {
+ context.logger
+ }
+
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt
index 811220af..b41e11fd 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt
@@ -1,6 +1,6 @@
package hep.dataforge.context
-import hep.dataforge.meta.DFExperimental
+import hep.dataforge.misc.DFExperimental
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.provider.Provider
@@ -48,8 +48,9 @@ public fun Context.gather(
putAll(top(target, type))
plugins.forEach { plugin ->
plugin.top(target, type).forEach { (name, value) ->
- if (containsKey(name)) error("Name conflict during gather. An item with name $name could not be gathered from $plugin because key is already present.")
- put(plugin.name + name, value)
+ val itemName = plugin.name + name
+ if (containsKey(itemName)) error("Name conflict during gather. An item with name $name could not be gathered from $plugin because key is already present.")
+ put(itemName, value)
}
}
if (inherit) {
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt
index 329662b5..86c6bcde 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt
@@ -1,11 +1,12 @@
package hep.dataforge.properties
import hep.dataforge.meta.Config
-import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.get
+import hep.dataforge.meta.set
import hep.dataforge.meta.transformations.MetaConverter
import hep.dataforge.meta.transformations.nullableItemToObject
import hep.dataforge.meta.transformations.nullableObjectToMetaItem
+import hep.dataforge.misc.DFExperimental
import hep.dataforge.names.Name
@DFExperimental
@@ -18,7 +19,7 @@ public class ConfigProperty(
override var value: T?
get() = converter.nullableItemToObject(config[name])
set(value) {
- config.setItem(name,converter.nullableObjectToMetaItem(value))
+ config[name] = converter.nullableObjectToMetaItem(value)
}
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt
index 987cfe4c..44e97ea9 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt
@@ -1,6 +1,6 @@
package hep.dataforge.properties
-import hep.dataforge.meta.DFExperimental
+import hep.dataforge.misc.DFExperimental
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt
index 6cd0d7fa..149a9ece 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt
@@ -19,39 +19,39 @@ import hep.dataforge.names.Name
import hep.dataforge.names.toName
/**
- *
- *
* Path interface.
*
- * @author Alexander Nozik
- * @version $Id: $Id
*/
public inline class Path(public val tokens: List) : Iterable {
- public val head: PathToken? get() = tokens.firstOrNull()
-
- public val length: Int get() = tokens.size
-
- /**
- * Returns non-empty optional containing the chain without first segment in case of chain path.
- *
- * @return
- */
- public val tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1))
-
override fun iterator(): Iterator = tokens.iterator()
+ override fun toString(): String = tokens.joinToString(separator = PATH_SEGMENT_SEPARATOR)
+
public companion object {
public const val PATH_SEGMENT_SEPARATOR: String = "/"
public fun parse(path: String): Path {
val head = path.substringBefore(PATH_SEGMENT_SEPARATOR)
val tail = path.substringAfter(PATH_SEGMENT_SEPARATOR)
- return PathToken.parse(head).toPath() + parse(tail)
+ return PathToken.parse(head).asPath() + parse(tail)
}
}
}
+public val Path.length: Int get() = tokens.size
+
+public val Path.head: PathToken? get() = tokens.firstOrNull()
+
+
+/**
+ * Returns non-empty optional containing the chain without first segment in case of chain path.
+ *
+ * @return
+ */
+public val Path.tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1))
+
+
public operator fun Path.plus(path: Path): Path = Path(this.tokens + path.tokens)
public data class PathToken(val name: Name, val target: String? = null) {
@@ -72,4 +72,22 @@ public data class PathToken(val name: Name, val target: String? = null) {
}
}
-public fun PathToken.toPath(): Path = Path(listOf(this))
+/**
+ * Represent this path token as full path
+ */
+public fun PathToken.asPath(): Path = Path(listOf(this))
+
+/**
+ * Represent a name with optional [target] as a [Path]
+ */
+public fun Name.asPath(target: String? = null): Path = PathToken(this, target).asPath()
+
+/**
+ * Build a path from given names using default targets
+ */
+public fun Path(vararg names: Name): Path = Path(names.map { PathToken(it) })
+
+/**
+ * Use an array of [Name]-target pairs to construct segmented [Path]
+ */
+public fun Path(vararg tokens: Pair): Path = Path(tokens.map { PathToken(it.first, it.second) })
\ No newline at end of file
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt
index 3a153d86..8d3ae30d 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt
@@ -84,6 +84,6 @@ public fun Provider.top(target: String, type: KClass): Map Provider.top(target: String): Map = top(target, T::class)
+public inline fun Provider.top(target: String ): Map = top(target, T::class)
diff --git a/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt b/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt
index 58647400..3d15e8b3 100644
--- a/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt
+++ b/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt
@@ -17,13 +17,13 @@ class ContextTest {
else -> emptyMap()
}
}
+
}
@Test
fun testPluginManager() {
- val context = Global.context("test"){
- plugin(DummyPlugin())
- }
+ val context = Global.context("test")
+ context.plugins.load(DummyPlugin())
//Global.plugins.load(DummyPlugin())
val members = context.gather("test")
assertEquals(3, members.count())
diff --git a/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt b/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt
index 05818cae..86296e39 100644
--- a/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt
+++ b/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt
@@ -1,10 +1,10 @@
package hep.dataforge.properties
-import hep.dataforge.meta.DFExperimental
+import hep.dataforge.misc.DFExperimental
import org.w3c.dom.HTMLInputElement
@DFExperimental
-fun HTMLInputElement.bindValue(property: Property) {
+public fun HTMLInputElement.bindValue(property: Property) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.value
@@ -18,7 +18,7 @@ fun HTMLInputElement.bindValue(property: Property) {
}
@DFExperimental
-fun HTMLInputElement.bindChecked(property: Property) {
+public fun HTMLInputElement.bindChecked(property: Property) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.checked
diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
index cadd4231..f88cee99 100644
--- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
+++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
@@ -16,7 +16,6 @@
package hep.dataforge.descriptors
-import hep.dataforge.meta.DFExperimental
import hep.dataforge.values.ValueType
import kotlin.reflect.KClass
diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt
index 9c08d6a8..6f7855a9 100644
--- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt
+++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt
@@ -2,7 +2,8 @@ package hep.dataforge.provider
import hep.dataforge.context.Context
import hep.dataforge.context.gather
-import hep.dataforge.meta.DFExperimental
+import hep.dataforge.misc.DFExperimental
+import hep.dataforge.misc.Type
import hep.dataforge.names.Name
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
diff --git a/dataforge-data/api/dataforge-data.api b/dataforge-data/api/dataforge-data.api
index 06e99242..709a8038 100644
--- a/dataforge-data/api/dataforge-data.api
+++ b/dataforge-data/api/dataforge-data.api
@@ -26,6 +26,24 @@ public final class hep/dataforge/data/ActionKt {
public static final fun then (Lhep/dataforge/data/Action;Lhep/dataforge/data/Action;)Lhep/dataforge/data/Action;
}
+public final class hep/dataforge/data/ComputationData : hep/dataforge/data/ComputationGoal, hep/dataforge/data/Data {
+ public fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V
+ public synthetic fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun getMeta ()Lhep/dataforge/meta/Meta;
+ public fun getType ()Lkotlin/reflect/KClass;
+ public fun toMeta ()Lhep/dataforge/meta/Meta;
+}
+
+public class hep/dataforge/data/ComputationGoal : hep/dataforge/data/Goal {
+ public fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V
+ public synthetic fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun getBlock ()Lkotlin/jvm/functions/Function2;
+ public fun getDependencies ()Ljava/util/Collection;
+ public final fun getResult ()Lkotlinx/coroutines/Deferred;
+ public fun reset ()V
+ public fun startAsync (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Deferred;
+}
+
public final class hep/dataforge/data/CoroutineMonitor : kotlin/coroutines/CoroutineContext$Element {
public static final field Companion Lhep/dataforge/data/CoroutineMonitor$Companion;
public fun ()V
@@ -79,14 +97,6 @@ public final class hep/dataforge/data/Data$DefaultImpls {
public static fun toMeta (Lhep/dataforge/data/Data;)Lhep/dataforge/meta/Meta;
}
-public final class hep/dataforge/data/DataCastKt {
- public static final fun canCast (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Z
- public static final fun cast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data;
- public static final fun cast (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode;
- public static final fun ensureType (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)V
- public static final fun upcast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data;
-}
-
public final class hep/dataforge/data/DataFilter : hep/dataforge/meta/Scheme {
public static final field Companion Lhep/dataforge/data/DataFilter$Companion;
public fun ()V
@@ -129,17 +139,22 @@ public final class hep/dataforge/data/DataItem$Node : hep/dataforge/data/DataIte
}
public final class hep/dataforge/data/DataJVMKt {
+ public static final fun canCast (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Z
+ public static final fun cast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data;
+ public static final fun cast (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode;
+ public static final fun ensureType (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)V
public static final fun filterIsInstance (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data;
public static final fun filterIsInstance (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataItem;
public static final fun filterIsInstance (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode;
public static final fun get (Lhep/dataforge/data/Data;)Ljava/lang/Object;
+ public static final fun upcast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data;
}
public final class hep/dataforge/data/DataKt {
public static final fun map (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/Data;
public static synthetic fun map$default (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/Data;
- public static final fun reduce (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/DynamicData;
- public static synthetic fun reduce$default (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/DynamicData;
+ public static final fun reduce (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/ComputationData;
+ public static synthetic fun reduce$default (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/ComputationData;
}
public abstract interface class hep/dataforge/data/DataNode : hep/dataforge/meta/MetaRepr {
@@ -148,50 +163,36 @@ public abstract interface class hep/dataforge/data/DataNode : hep/dataforge/meta
public abstract fun getItems ()Ljava/util/Map;
public abstract fun getMeta ()Lhep/dataforge/meta/Meta;
public abstract fun getType ()Lkotlin/reflect/KClass;
- public abstract fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public abstract fun toMeta ()Lhep/dataforge/meta/Meta;
}
public final class hep/dataforge/data/DataNode$Companion {
public static final field TYPE Ljava/lang/String;
public final fun builder (Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataTreeBuilder;
- public final fun invoke (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/data/DataTree;
}
public final class hep/dataforge/data/DataNode$DefaultImpls {
- public static fun startAll (Lhep/dataforge/data/DataNode;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public static fun toMeta (Lhep/dataforge/data/DataNode;)Lhep/dataforge/meta/Meta;
}
public final class hep/dataforge/data/DataNodeKt {
- public static final fun asSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence;
- public static final fun builder (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/DataTreeBuilder;
public static final fun dataSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence;
- public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/Data;)V
- public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/Data;)V
public static final fun filter (Lhep/dataforge/data/DataNode;Lkotlin/jvm/functions/Function2;)Lhep/dataforge/data/DataNode;
public static final fun first (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/Data;
public static final fun get (Lhep/dataforge/data/DataNode;Lhep/dataforge/names/Name;)Lhep/dataforge/data/DataItem;
public static final fun get (Lhep/dataforge/data/DataNode;Ljava/lang/String;)Lhep/dataforge/data/DataItem;
public static final fun getData (Lhep/dataforge/data/DataItem;)Lhep/dataforge/data/Data;
public static final fun getNode (Lhep/dataforge/data/DataItem;)Lhep/dataforge/data/DataNode;
+ public static final fun itemSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence;
public static final fun iterator (Lhep/dataforge/data/DataNode;)Ljava/util/Iterator;
public static final fun join (Lhep/dataforge/data/DataNode;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/DataNode;)V
- public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/DataNode;)V
- public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;)V
- public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
- public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)V
- public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
- public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public static final fun startAll (Lhep/dataforge/data/DataNode;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
}
public final class hep/dataforge/data/DataTree : hep/dataforge/data/DataNode {
public fun getItems ()Ljava/util/Map;
public fun getMeta ()Lhep/dataforge/meta/Meta;
public fun getType ()Lkotlin/reflect/KClass;
- public fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public fun toMeta ()Lhep/dataforge/meta/Meta;
}
@@ -214,6 +215,21 @@ public final class hep/dataforge/data/DataTreeBuilder {
public final fun update (Lhep/dataforge/data/DataNode;)V
}
+public final class hep/dataforge/data/DataTreeBuilderKt {
+ public static final fun DataTree (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/data/DataTree;
+ public static final fun builder (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/DataTreeBuilder;
+ public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/Data;)V
+ public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/Data;)V
+ public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/DataNode;)V
+ public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/DataNode;)V
+ public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;)V
+ public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+ public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)V
+ public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+}
+
public final class hep/dataforge/data/Dependencies : kotlin/coroutines/CoroutineContext$Element {
public static final field Companion Lhep/dataforge/data/Dependencies$Companion;
public fun (Ljava/util/Collection;)V
@@ -228,24 +244,6 @@ public final class hep/dataforge/data/Dependencies : kotlin/coroutines/Coroutine
public final class hep/dataforge/data/Dependencies$Companion : kotlin/coroutines/CoroutineContext$Key {
}
-public final class hep/dataforge/data/DynamicData : hep/dataforge/data/DynamicGoal, hep/dataforge/data/Data {
- public fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V
- public synthetic fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public fun getMeta ()Lhep/dataforge/meta/Meta;
- public fun getType ()Lkotlin/reflect/KClass;
- public fun toMeta ()Lhep/dataforge/meta/Meta;
-}
-
-public class hep/dataforge/data/DynamicGoal : hep/dataforge/data/Goal {
- public fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V
- public synthetic fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun getBlock ()Lkotlin/jvm/functions/Function2;
- public fun getDependencies ()Ljava/util/Collection;
- public final fun getResult ()Lkotlinx/coroutines/Deferred;
- public fun reset ()V
- public fun startAsync (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Deferred;
-}
-
public final class hep/dataforge/data/FragmentRule {
public field result Lkotlin/jvm/functions/Function2;
public fun (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaBuilder;)V
@@ -302,9 +300,7 @@ public final class hep/dataforge/data/JoinGroup {
}
public final class hep/dataforge/data/MapAction : hep/dataforge/data/Action {
- public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
- public final fun getInputType ()Lkotlin/reflect/KClass;
- public final fun getOutputType ()Lkotlin/reflect/KClass;
+ public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode;
public fun isTerminal ()Z
}
@@ -335,9 +331,7 @@ public final class hep/dataforge/data/NamedData : hep/dataforge/data/Data {
}
public final class hep/dataforge/data/ReduceAction : hep/dataforge/data/Action {
- public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
- public final fun getInputType ()Lkotlin/reflect/KClass;
- public final fun getOutputType ()Lkotlin/reflect/KClass;
+ public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode;
public fun isTerminal ()Z
}
@@ -357,9 +351,7 @@ public final class hep/dataforge/data/ReduceGroupBuilder {
}
public final class hep/dataforge/data/SplitAction : hep/dataforge/data/Action {
- public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
- public final fun getInputType ()Lkotlin/reflect/KClass;
- public final fun getOutputType ()Lkotlin/reflect/KClass;
+ public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode;
public fun isTerminal ()Z
}
@@ -394,7 +386,6 @@ public final class hep/dataforge/data/TypeFilteredDataNode : hep/dataforge/data/
public fun getMeta ()Lhep/dataforge/meta/Meta;
public final fun getOrigin ()Lhep/dataforge/data/DataNode;
public fun getType ()Lkotlin/reflect/KClass;
- public fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public fun toMeta ()Lhep/dataforge/meta/Meta;
}
diff --git a/dataforge-data/build.gradle.kts b/dataforge-data/build.gradle.kts
index 436d9428..16d98ced 100644
--- a/dataforge-data/build.gradle.kts
+++ b/dataforge-data/build.gradle.kts
@@ -12,12 +12,12 @@ kotlin {
commonMain{
dependencies {
api(project(":dataforge-meta"))
- }
- }
- jvmMain{
- dependencies{
api(kotlin("reflect"))
}
}
}
-}
\ No newline at end of file
+}
+
+readme{
+ maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL
+}
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt
new file mode 100644
index 00000000..85ec2977
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt
@@ -0,0 +1,41 @@
+package hep.dataforge.actions
+
+import hep.dataforge.data.DataSet
+import hep.dataforge.meta.Meta
+import hep.dataforge.misc.DFExperimental
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * A simple data transformation on a data node. Actions should avoid doing actual dependency evaluation in [execute].
+ */
+public interface Action {
+ /**
+ * Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy
+ * so not actual computation is started at this moment.
+ *
+ * [scope] context used to compute the initial result, also it is used for updates propagation
+ */
+ public suspend fun execute(dataSet: DataSet, meta: Meta = Meta.EMPTY, scope: CoroutineScope? = null): DataSet
+
+ public companion object
+}
+
+/**
+ * Action composition. The result is terminal if one of its parts is terminal
+ */
+public infix fun Action.then(action: Action): Action {
+ // TODO introduce composite action and add optimize by adding action to the list
+ return object : Action {
+ override suspend fun execute(dataSet: DataSet, meta: Meta, scope: CoroutineScope?): DataSet {
+ return action.execute(this@then.execute(dataSet, meta, scope), meta, scope)
+ }
+ }
+}
+
+@DFExperimental
+public suspend fun DataSet.transformWith(
+ action: Action,
+ meta: Meta = Meta.EMPTY,
+ scope: CoroutineScope? = null,
+): DataSet = action.execute(this, meta, scope)
+
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt
new file mode 100644
index 00000000..23731621
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt
@@ -0,0 +1,104 @@
+package hep.dataforge.actions
+
+import hep.dataforge.data.*
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBuilder
+import hep.dataforge.meta.seal
+import hep.dataforge.meta.toMutableMeta
+import hep.dataforge.misc.DFBuilder
+import hep.dataforge.misc.DFExperimental
+import hep.dataforge.misc.DFInternal
+import hep.dataforge.names.Name
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlin.reflect.KType
+import kotlin.reflect.typeOf
+
+/**
+ * Action environment includes data name, data meta and action configuration meta
+ */
+public data class ActionEnv(
+ val name: Name,
+ val meta: Meta,
+ val actionMeta: Meta,
+)
+
+/**
+ * Action environment
+ */
+@DFBuilder
+public class MapActionBuilder(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) {
+ public lateinit var result: suspend ActionEnv.(T) -> R
+
+ /**
+ * Calculate the result of goal
+ */
+ public fun result(f: suspend ActionEnv.(T) -> R) {
+ result = f;
+ }
+}
+
+@PublishedApi
+internal class MapAction(
+ private val outputType: KType,
+ private val block: MapActionBuilder.() -> Unit,
+) : Action {
+
+ override suspend fun execute(
+ dataSet: DataSet,
+ meta: Meta,
+ scope: CoroutineScope?,
+ ): DataSet {
+ suspend fun mapOne(data: NamedData): NamedData {
+ // Creating a new environment for action using **old** name, old meta and task meta
+ val env = ActionEnv(data.name, data.meta, meta)
+
+ //applying transformation from builder
+ val builder = MapActionBuilder(
+ data.name,
+ data.meta.toMutableMeta(), // using data meta
+ meta
+ ).apply(block)
+
+ //getting new name
+ val newName = builder.name
+
+ //getting new meta
+ val newMeta = builder.meta.seal()
+
+ @OptIn(DFInternal::class) val newData = Data(outputType, newMeta, dependencies = listOf(data)) {
+ builder.result(env, data.await())
+ }
+ //setting the data node
+ return newData.named(newName)
+ }
+
+ val flow = dataSet.flow().map(::mapOne)
+
+ return ActiveDataTree(outputType) {
+ populate(flow)
+ scope?.launch {
+ dataSet.updates.collect { name ->
+ //clear old nodes
+ remove(name)
+ //collect new items
+ populate(dataSet.flowChildren(name).map(::mapOne))
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * A one-to-one mapping action
+ */
+@DFExperimental
+@Suppress("FunctionName")
+public inline fun Action.Companion.map(
+ noinline builder: MapActionBuilder.() -> Unit,
+): Action = MapAction(typeOf(), builder)
+
+
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt
new file mode 100644
index 00000000..8e2781b5
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt
@@ -0,0 +1,116 @@
+package hep.dataforge.actions
+
+import hep.dataforge.data.*
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBuilder
+import hep.dataforge.misc.DFBuilder
+import hep.dataforge.misc.DFExperimental
+import hep.dataforge.misc.DFInternal
+import hep.dataforge.names.Name
+import hep.dataforge.names.toName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.fold
+import kotlin.reflect.KType
+import kotlin.reflect.typeOf
+
+
+public class JoinGroup(public var name: String, internal val set: DataSet) {
+
+ public var meta: MetaBuilder = MetaBuilder()
+
+ public lateinit var result: suspend ActionEnv.(Map) -> R
+
+ public fun result(f: suspend ActionEnv.(Map) -> R) {
+ this.result = f;
+ }
+
+}
+
+@DFBuilder
+public class ReduceGroupBuilder(
+ private val inputType: KType,
+ private val scope: CoroutineScope,
+ public val actionMeta: Meta,
+) {
+ private val groupRules: MutableList) -> List>> = ArrayList();
+
+ /**
+ * introduce grouping by meta value
+ */
+ public fun byValue(tag: String, defaultTag: String = "@default", action: JoinGroup.() -> Unit) {
+ groupRules += { node ->
+ GroupRule.byMetaValue(scope, tag, defaultTag).gather(node).map {
+ JoinGroup(it.key, it.value).apply(action)
+ }
+ }
+ }
+
+ public fun group(
+ groupName: String,
+ filter: suspend (Name, Data) -> Boolean,
+ action: JoinGroup.() -> Unit,
+ ) {
+ groupRules += { source ->
+ listOf(
+ JoinGroup(groupName, source.filter(filter)).apply(action)
+ )
+ }
+ }
+
+ /**
+ * Apply transformation to the whole node
+ */
+ public fun result(resultName: String, f: suspend ActionEnv.(Map) -> R) {
+ groupRules += { node ->
+ listOf(JoinGroup(resultName, node).apply { result(f) })
+ }
+ }
+
+ internal suspend fun buildGroups(input: DataSet): List> {
+ return groupRules.flatMap { it.invoke(input) }
+ }
+
+}
+
+@PublishedApi
+internal class ReduceAction(
+ private val inputType: KType,
+ outputType: KType,
+ private val action: ReduceGroupBuilder.() -> Unit,
+) : CachingAction(outputType) {
+ //TODO optimize reduction. Currently the whole action recalculates on push
+
+
+ override fun CoroutineScope.transform(set: DataSet, meta: Meta, key: Name): Flow> = flow {
+ ReduceGroupBuilder(inputType, this@transform, meta).apply(action).buildGroups(set).forEach { group ->
+ val dataFlow: Map> = group.set.flow().fold(HashMap()) { acc, value ->
+ acc.apply {
+ acc[value.name] = value.data
+ }
+ }
+
+ val groupName: String = group.name
+
+ val groupMeta = group.meta
+
+ val env = ActionEnv(groupName.toName(), groupMeta, meta)
+ @OptIn(DFInternal::class) val res: Data = dataFlow.reduceToData(
+ outputType,
+ meta = groupMeta
+ ) { group.result.invoke(env, it) }
+
+ emit(res.named(env.name))
+ }
+ }
+}
+
+/**
+ * A one-to-one mapping action
+ */
+@DFExperimental
+@Suppress("FunctionName")
+public inline fun Action.Companion.reduce(
+ noinline builder: ReduceGroupBuilder.() -> Unit,
+): Action = ReduceAction(typeOf(), typeOf(), builder)
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt
new file mode 100644
index 00000000..37b8f734
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt
@@ -0,0 +1,96 @@
+package hep.dataforge.actions
+
+import hep.dataforge.data.*
+import hep.dataforge.meta.Laminate
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBuilder
+import hep.dataforge.meta.toMutableMeta
+import hep.dataforge.misc.DFExperimental
+import hep.dataforge.misc.DFInternal
+import hep.dataforge.names.Name
+import hep.dataforge.names.toName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+import kotlin.collections.set
+import kotlin.reflect.KType
+import kotlin.reflect.typeOf
+
+
+public class SplitBuilder(public val name: Name, public val meta: Meta) {
+
+ public class FragmentRule(public val name: Name, public var meta: MetaBuilder) {
+ public lateinit var result: suspend (T) -> R
+
+ public fun result(f: suspend (T) -> R) {
+ result = f;
+ }
+ }
+
+ internal val fragments: MutableMap.() -> Unit> = HashMap()
+
+ /**
+ * Add new fragment building rule. If the framgent not defined, result won't be available even if it is present in the map
+ * @param name the name of a fragment
+ * @param rule the rule to transform fragment name and meta using
+ */
+ public fun fragment(name: String, rule: FragmentRule.() -> Unit) {
+ fragments[name.toName()] = rule
+ }
+}
+
+/**
+ * Action that splits each incoming element into a number of fragments defined in builder
+ */
+@PublishedApi
+internal class SplitAction(
+ private val outputType: KType,
+ private val action: SplitBuilder.() -> Unit,
+) : Action {
+
+ @OptIn(FlowPreview::class)
+ override suspend fun execute(
+ dataSet: DataSet,
+ meta: Meta,
+ scope: CoroutineScope?,
+ ): DataSet {
+
+ suspend fun splitOne(data: NamedData): Flow> {
+ val laminate = Laminate(data.meta, meta)
+
+ val split = SplitBuilder(data.name, data.meta).apply(action)
+
+
+ // apply individual fragment rules to result
+ return split.fragments.entries.asFlow().map { (fragmentName, rule) ->
+ val env = SplitBuilder.FragmentRule(fragmentName, laminate.toMutableMeta()).apply(rule)
+ //data.map(outputType, meta = env.meta) { env.result(it) }.named(fragmentName)
+ @OptIn(DFInternal::class) Data(outputType, meta = env.meta, dependencies = listOf(data)) {
+ env.result(data.await())
+ }.named(fragmentName)
+ }
+ }
+
+ return ActiveDataTree(outputType) {
+ populate(dataSet.flow().flatMapConcat(transform = ::splitOne))
+ scope?.launch {
+ dataSet.updates.collect { name ->
+ //clear old nodes
+ remove(name)
+ //collect new items
+ populate(dataSet.flowChildren(name).flatMapConcat(transform = ::splitOne))
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Action that splits each incoming element into a number of fragments defined in builder
+ */
+@DFExperimental
+@Suppress("FunctionName")
+public inline fun Action.Companion.split(
+ noinline builder: SplitBuilder.() -> Unit,
+): Action = SplitAction(typeOf(), builder)
\ No newline at end of file
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt
deleted file mode 100644
index d747587e..00000000
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package hep.dataforge.data
-
-import hep.dataforge.meta.Meta
-
-/**
- * A simple data transformation on a data node
- */
-public interface Action {
- /**
- * Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy
- * so not actual computation is started at this moment
- */
- public operator fun invoke(node: DataNode, meta: Meta): DataNode
-
- /**
- * Terminal action is the one that could not be invoked lazily and requires some kind of blocking computation to invoke
- */
- public val isTerminal: Boolean get() = false
-}
-
-/**
- * Action composition. The result is terminal if one of its parts is terminal
- */
-public infix fun Action.then(action: Action): Action {
- // TODO introduce composite action and add optimize by adding action to the list
- return object : Action {
- override fun invoke(node: DataNode, meta: Meta): DataNode {
- return action(this@then.invoke(node, meta), meta)
- }
-
- override val isTerminal: Boolean
- get() = this@then.isTerminal || action.isTerminal
- }
-}
-
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt
new file mode 100644
index 00000000..5d197982
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt
@@ -0,0 +1,118 @@
+package hep.dataforge.data
+
+import hep.dataforge.meta.*
+import hep.dataforge.names.*
+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 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.toName(), ActiveDataTree(typeOf(), block))
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt
new file mode 100644
index 00000000..7911cb1f
--- /dev/null
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt
@@ -0,0 +1,52 @@
+package hep.dataforge.data
+
+import hep.dataforge.actions.Action
+import hep.dataforge.meta.Meta
+import hep.dataforge.names.Name
+import hep.dataforge.names.startsWith
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlin.reflect.KType
+
+/**
+ * Remove all values with keys starting with [name]
+ */
+internal fun MutableMap.removeWhatStartsWith(name: Name) {
+ val toRemove = keys.filter { it.startsWith(name) }
+ toRemove.forEach(::remove)
+}
+
+/**
+ * An action that caches results on-demand and recalculates them on source push
+ */
+public abstract class CachingAction(
+ public val outputType: KType,
+) : Action {
+
+ protected abstract fun CoroutineScope.transform(
+ set: DataSet,
+ meta: Meta,
+ key: Name = Name.EMPTY,
+ ): Flow>
+
+ override suspend fun execute(
+ dataSet: DataSet,
+ meta: Meta,
+ scope: CoroutineScope?,
+ ): DataSet = ActiveDataTree(outputType) {
+ coroutineScope {
+ populate(transform(dataSet, meta))
+ }
+ scope?.let {
+ dataSet.updates.collect {
+ //clear old nodes
+ remove(it)
+ //collect new items
+ populate(scope.transform(dataSet, meta, it))
+ //FIXME if the target is data, updates are fired twice
+ }
+ }
+ }
+}
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt
index d1c0d55e..60bf5775 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt
@@ -1,6 +1,6 @@
package hep.dataforge.data
-import hep.dataforge.meta.DFExperimental
+import hep.dataforge.misc.DFExperimental
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
index c573aad8..6702de4d 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
@@ -3,28 +3,32 @@ package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.isEmpty
-import kotlinx.coroutines.CoroutineScope
+import hep.dataforge.misc.DFInternal
+import hep.dataforge.misc.Type
+import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.typeOf
/**
* A data element characterized by its meta
*/
-public interface Data : Goal, MetaRepr{
+@Type(Data.TYPE)
+public interface Data : Goal, MetaRepr {
/**
* Type marker for the data. The type is known before the calculation takes place so it could be checked.
*/
- public val type: KClass
+ public val type: KType
/**
* Meta for the data
*/
public val meta: Meta
- override fun toMeta(): Meta = Meta {
- "type" put (type.simpleName?:"undefined")
- if(!meta.isEmpty()) {
+ override fun toMeta(): Meta = Meta {
+ "type" put (type.toString())
+ if (!meta.isEmpty()) {
"meta" put meta
}
}
@@ -32,131 +36,67 @@ public interface Data : Goal, MetaRepr{
public companion object {
public const val TYPE: String = "data"
- public operator fun invoke(
- type: KClass,
- meta: Meta = Meta.EMPTY,
- context: CoroutineContext = EmptyCoroutineContext,
- dependencies: Collection> = emptyList(),
- block: suspend CoroutineScope.() -> T
- ): Data = DynamicData(type, meta, context, dependencies, block)
+ /**
+ * The type that can't have any subtypes
+ */
+ internal val TYPE_OF_NOTHING: KType = typeOf()
- public inline operator fun invoke(
+ public inline fun static(
+ value: T,
meta: Meta = Meta.EMPTY,
- context: CoroutineContext = EmptyCoroutineContext,
- dependencies: Collection> = emptyList(),
- noinline block: suspend CoroutineScope.() -> T
- ): Data = invoke(T::class, meta, context, dependencies, block)
+ ): Data = StaticData(typeOf(), value, meta)
- public operator fun invoke(
- name: String,
- type: KClass,
- meta: Meta = Meta.EMPTY,
- context: CoroutineContext = EmptyCoroutineContext,
- dependencies: Collection> = emptyList(),
- block: suspend CoroutineScope.() -> T
- ): Data = NamedData(name, invoke(type, meta, context, dependencies, block))
+ /**
+ * An empty data containing only meta
+ */
+ public fun empty(meta: Meta): Data = object : Data {
+ override val type: KType = TYPE_OF_NOTHING
+ override val meta: Meta = meta
+ override val dependencies: Collection> = emptyList()
+ override val deferred: Deferred
+ get() = GlobalScope.async(start = CoroutineStart.LAZY) {
+ error("The Data is empty and could not be computed")
+ }
- public inline operator fun invoke(
- name: String,
- meta: Meta = Meta.EMPTY,
- context: CoroutineContext = EmptyCoroutineContext,
- dependencies: Collection> = emptyList(),
- noinline block: suspend CoroutineScope.() -> T
- ): Data =
- invoke(name, T::class, meta, context, dependencies, block)
-
- public fun static(value: T, meta: Meta = Meta.EMPTY): Data =
- StaticData(value, meta)
+ override fun async(coroutineScope: CoroutineScope): Deferred = deferred
+ override fun reset() {}
+ }
}
}
-
-public class DynamicData(
- override val type: KClass,
+/**
+ * A lazily computed variant of [Data] based on [LazyGoal]
+ * One must ensure that proper [type] is used so this method should not be used
+ */
+private class LazyData(
+ override val type: KType,
override val meta: Meta = Meta.EMPTY,
- context: CoroutineContext = EmptyCoroutineContext,
+ additionalContext: CoroutineContext = EmptyCoroutineContext,
dependencies: Collection> = emptyList(),
- block: suspend CoroutineScope.() -> T
-) : Data, DynamicGoal(context, dependencies, block)
+ block: suspend () -> T,
+) : Data, LazyGoal(additionalContext, dependencies, block)
public class StaticData(
+ override val type: KType,
value: T,
- override val meta: Meta = Meta.EMPTY
-) : Data, StaticGoal(value) {
- override val type: KClass get() = value::class
-}
-
-public class NamedData(public val name: String, data: Data) : Data by data
-
-public fun Data.map(
- outputType: KClass,
- coroutineContext: CoroutineContext = EmptyCoroutineContext,
- meta: Meta = this.meta,
- block: suspend CoroutineScope.(T) -> R
-): Data = DynamicData(outputType, meta, coroutineContext, listOf(this)) {
- block(await())
-}
-
-
-/**
- * Create a data pipe
- */
-public inline fun