From 946ac88480342b0b96205fcec182c75435953ecc Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 5 Dec 2023 15:13:50 +0300 Subject: [PATCH] Partially fixed a bug with `MutableMeta` observable wrappers. --- CHANGELOG.md | 2 + dataforge-context/build.gradle.kts | 6 ++- .../kscience/dataforge/context/loggingWasm.kt | 3 ++ dataforge-data/build.gradle.kts | 4 +- .../kscience/dataforge/data/ActionsTest.kt | 6 +-- dataforge-io/build.gradle.kts | 3 +- dataforge-meta/api/dataforge-meta.api | 6 ++- dataforge-meta/build.gradle.kts | 1 + .../dataforge/meta/ObservableMetaWrapper.kt | 41 ++++++++++------ .../space/kscience/dataforge/names/Name.kt | 4 ++ .../dataforge/meta/ObservableMetaTest.kt | 49 +++++++++++++++++++ .../space/kscience/dataforge/misc/castJs.kt | 4 +- .../kscience/dataforge/misc/castNative.kt | 2 +- .../space/kscience/dataforge/misc/castWasm.kt | 4 ++ dataforge-scripting/build.gradle.kts | 6 +-- dataforge-workspace/build.gradle.kts | 26 +++++----- gradle.properties | 2 +- 17 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt create mode 100644 dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt create mode 100644 dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index e32cc6bf..87ccad00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added +- Wasm artifacts ### Changed @@ -11,6 +12,7 @@ ### Removed ### Fixed +- Partially fixed a bug with `MutableMeta` observable wrappers. ### Security diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts index be9036d0..b59abed0 100644 --- a/dataforge-context/build.gradle.kts +++ b/dataforge-context/build.gradle.kts @@ -8,12 +8,14 @@ kscience { jvm() js() native() + wasm() useCoroutines() useSerialization() - dependencies { + commonMain { api(project(":dataforge-meta")) + api(spclibs.atomicfu) } - dependencies(jvmMain){ + jvmMain{ api(kotlin("reflect")) api("org.slf4j:slf4j-api:1.7.30") } diff --git a/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt b/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt new file mode 100644 index 00000000..740957b4 --- /dev/null +++ b/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt @@ -0,0 +1,3 @@ +package space.kscience.dataforge.context + +internal actual fun getGlobalLoggerFactory(): PluginFactory = DefaultLogManager \ No newline at end of file diff --git a/dataforge-data/build.gradle.kts b/dataforge-data/build.gradle.kts index 9f96604a..ea542290 100644 --- a/dataforge-data/build.gradle.kts +++ b/dataforge-data/build.gradle.kts @@ -6,9 +6,11 @@ kscience{ jvm() js() native() + wasm() useCoroutines() dependencies { - api(project(":dataforge-meta")) + api(spclibs.atomicfu) + api(projects.dataforgeMeta) api(kotlin("reflect")) } } diff --git a/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt b/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt index 3987cd19..b24c4f27 100644 --- a/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt +++ b/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import space.kscience.dataforge.actions.Action import space.kscience.dataforge.actions.invoke -import space.kscience.dataforge.actions.map +import space.kscience.dataforge.actions.mapping import space.kscience.dataforge.misc.DFExperimental import kotlin.test.assertEquals @@ -20,7 +20,7 @@ internal class ActionsTest { } } - val plusOne = Action.map { + val plusOne = Action.mapping { result { it + 1 } } val result = plusOne(data) @@ -31,7 +31,7 @@ internal class ActionsTest { fun testDynamicMapAction() = runTest { val data: DataSourceBuilder = DataSource() - val plusOne = Action.map { + val plusOne = Action.mapping { result { it + 1 } } diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index 6d3c888c..f7197197 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -4,12 +4,13 @@ plugins { description = "IO module" -val ioVersion = "0.2.1" +val ioVersion = "0.3.0" kscience { jvm() js() native() + wasm() useSerialization() useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST) { cbor() diff --git a/dataforge-meta/api/dataforge-meta.api b/dataforge-meta/api/dataforge-meta.api index 7daa7540..1700ca7d 100644 --- a/dataforge-meta/api/dataforge-meta.api +++ b/dataforge-meta/api/dataforge-meta.api @@ -724,6 +724,7 @@ public final class space/kscience/dataforge/meta/descriptors/MetaDescriptorBuild public final fun getAttributes ()Lspace/kscience/dataforge/meta/MutableMeta; public final fun getChildren ()Ljava/util/Map; public final fun getDefault ()Lspace/kscience/dataforge/meta/Value; + public final fun getDescription ()Ljava/lang/String; public final fun getIndexKey ()Ljava/lang/String; public final fun getInfo ()Ljava/lang/String; public final fun getMultiple ()Z @@ -737,6 +738,7 @@ public final class space/kscience/dataforge/meta/descriptors/MetaDescriptorBuild public final fun setAttributes (Lspace/kscience/dataforge/meta/MutableMeta;)V public final fun setChildren (Ljava/util/Map;)V public final fun setDefault (Lspace/kscience/dataforge/meta/Value;)V + public final fun setDescription (Ljava/lang/String;)V public final fun setIndexKey (Ljava/lang/String;)V public final fun setInfo (Ljava/lang/String;)V public final fun setMultiple (Z)V @@ -903,7 +905,7 @@ public abstract interface annotation class space/kscience/dataforge/misc/DFExper public abstract interface annotation class space/kscience/dataforge/misc/DFInternal : java/lang/annotation/Annotation { } -public abstract interface annotation class space/kscience/dataforge/misc/DfId : java/lang/annotation/Annotation { +public abstract interface annotation class space/kscience/dataforge/misc/DfType : java/lang/annotation/Annotation { public abstract fun id ()Ljava/lang/String; } @@ -944,6 +946,7 @@ public final class space/kscience/dataforge/names/NameKt { public static final fun asName (Lspace/kscience/dataforge/names/NameToken;)Lspace/kscience/dataforge/names/Name; public static final fun cutFirst (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name; public static final fun cutLast (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name; + public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Z public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Z public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/NameToken;)Z public static final fun first (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/NameToken; @@ -966,6 +969,7 @@ public final class space/kscience/dataforge/names/NameKt { public static final fun removeHeadOrNull (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name; public static final fun replaceLast (Lspace/kscience/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)Lspace/kscience/dataforge/names/Name; public static final fun set (Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)V + public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Z public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Z public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/NameToken;)Z public static final fun withIndex (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Lspace/kscience/dataforge/names/Name; diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts index 51b07113..d150ef98 100644 --- a/dataforge-meta/build.gradle.kts +++ b/dataforge-meta/build.gradle.kts @@ -6,6 +6,7 @@ kscience { jvm() js() native() + wasm() useSerialization{ json() } diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt index 76645d83..71e15aa9 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt @@ -6,60 +6,71 @@ import space.kscience.dataforge.names.* /** * A class that takes [MutableMeta] provider and adds obsevability on top of that + * + * TODO rewrite to properly work with detached nodes */ private class ObservableMetaWrapper( val root: MutableMeta, - val absoluteName: Name, + val nodeName: Name, val listeners: MutableSet, ) : ObservableMutableMeta { override val items: Map get() = root.items.keys.associateWith { - ObservableMetaWrapper(root, absoluteName + it, listeners) + ObservableMetaWrapper(root, nodeName + it, listeners) } - override fun get(name: Name): ObservableMutableMeta? = - root.get(name)?.let { ObservableMetaWrapper(root, this.absoluteName + name, listeners) } + override fun get(name: Name): ObservableMutableMeta? = if (root[nodeName + name] == null) { + null + } else { + ObservableMetaWrapper(root, nodeName + name, listeners) + } @ThreadSafe override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { listeners.add( - MetaListener(Pair(owner, absoluteName)) { name -> - if (name.startsWith(absoluteName)) { - (this[absoluteName] ?: Meta.EMPTY).callback(name.removeFirstOrNull(absoluteName)!!) + MetaListener(Pair(owner, nodeName)) { fullName -> + if (fullName.startsWith(nodeName)) { + root[nodeName]?.callback(fullName.removeFirstOrNull(nodeName)!!) } } ) } override fun removeListener(owner: Any?) { - listeners.removeAll { it.owner === Pair(owner, absoluteName) } + listeners.removeAll { it.owner === Pair(owner, nodeName) } } override fun invalidate(name: Name) { - listeners.forEach { it.callback(this, name) } + listeners.forEach { it.callback(this, nodeName + name) } } override var value: Value? - get() = root.value + get() = root[nodeName]?.value set(value) { - root.value = value + root.getOrCreate(nodeName).value = value invalidate(Name.EMPTY) } override fun getOrCreate(name: Name): ObservableMutableMeta = - ObservableMetaWrapper(root, this.absoluteName + name, listeners) + ObservableMetaWrapper(root, nodeName + name, listeners) - override fun set(name: Name, node: Meta?) { + fun removeNode(name: Name): Meta? { val oldMeta = get(name) //don't forget to remove listener oldMeta?.removeListener(this) - root.set(absoluteName + name, node) + + return oldMeta + } + + override fun set(name: Name, node: Meta?) { + val oldMeta = removeNode(name) + root[nodeName + name] = node if (oldMeta != node) { invalidate(name) } } - override fun toMeta(): Meta = root[absoluteName]?.toMeta() ?: Meta.EMPTY + override fun toMeta(): Meta = root[nodeName]?.toMeta() ?: Meta.EMPTY override fun toString(): String = Meta.toString(this) override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt index 160ea3a1..1c9a9cf3 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt @@ -216,9 +216,13 @@ public fun Name.endsWith(token: NameToken): Boolean = lastOrNull() == token public fun Name.startsWith(name: Name): Boolean = this.length >= name.length && (this == name || tokens.subList(0, name.length) == name.tokens) +public fun Name.startsWith(name: String): Boolean = startsWith(name.parseAsName()) + public fun Name.endsWith(name: Name): Boolean = this.length >= name.length && (this == name || tokens.subList(length - name.length, length) == name.tokens) +public fun Name.endsWith(name: String): Boolean = endsWith(name.parseAsName()) + /** * if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise, returns null */ diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt new file mode 100644 index 00000000..4681ec12 --- /dev/null +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt @@ -0,0 +1,49 @@ +package space.kscience.dataforge.meta + +import space.kscience.dataforge.names.startsWith +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals + +class ObservableMetaTest { + + @Test + fun asObservable() { + val meta = MutableMeta { + "data" put { + "x" put ListValue(1, 2, 3) + "y" put ListValue(5, 6, 7) + "type" put "scatter" + } + }.asObservable() + + assertEquals("scatter", meta["data.type"].string) + } + + @Test + @Ignore + fun detachNode() { + val meta = MutableMeta { + "data" put { + "x" put ListValue(1, 2, 3) + "y" put ListValue(5, 6, 7) + "type" put "scatter" + } + }.asObservable() + + var collector: Value? = null + + meta.onChange(null) { name -> + if (name.startsWith("data")) { + collector = get("data.z")?.value + } + } + + val data = meta["data"]!! + + meta.remove("data") + + data["z"] = ListValue(2, 5, 7) + assertEquals(null, collector) + } +} \ No newline at end of file diff --git a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt index b404ebb4..b057bcbe 100644 --- a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt +++ b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt @@ -1,5 +1,5 @@ package space.kscience.dataforge.misc import kotlin.js.unsafeCast as unsafeCastJs -@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") -public actual inline fun Any?.unsafeCast(): T = this.unsafeCastJs() \ No newline at end of file +@Suppress("NOTHING_TO_INLINE") +public actual inline fun Any?.unsafeCast(): T = unsafeCastJs() \ No newline at end of file diff --git a/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt b/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt index 27d399fe..4d9aa758 100644 --- a/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt +++ b/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt @@ -1,4 +1,4 @@ package space.kscience.dataforge.misc -@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") +@Suppress("UNCHECKED_CAST") public actual inline fun Any?.unsafeCast(): T = this as T \ No newline at end of file diff --git a/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt b/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt new file mode 100644 index 00000000..27d399fe --- /dev/null +++ b/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt @@ -0,0 +1,4 @@ +package space.kscience.dataforge.misc + +@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") +public actual inline fun Any?.unsafeCast(): T = this as T \ No newline at end of file diff --git a/dataforge-scripting/build.gradle.kts b/dataforge-scripting/build.gradle.kts index be81fe70..d9d87742 100644 --- a/dataforge-scripting/build.gradle.kts +++ b/dataforge-scripting/build.gradle.kts @@ -4,15 +4,15 @@ plugins { kscience{ jvm() - dependencies { + commonMain { api(projects.dataforgeWorkspace) implementation(kotlin("scripting-common")) } - dependencies(jvmMain){ + jvmMain{ implementation(kotlin("scripting-jvm-host")) implementation(kotlin("scripting-jvm")) } - dependencies(jvmTest){ + jvmTest{ implementation(spclibs.logback.classic) } } diff --git a/dataforge-workspace/build.gradle.kts b/dataforge-workspace/build.gradle.kts index ec117865..5fa555eb 100644 --- a/dataforge-workspace/build.gradle.kts +++ b/dataforge-workspace/build.gradle.kts @@ -2,29 +2,27 @@ plugins { id("space.kscience.gradle.mpp") } -kscience{ +kscience { jvm() js() native() + wasm() useCoroutines() - useSerialization{ + useSerialization { protobuf() } - commonMain{ - dependencies { - api(projects.dataforgeContext) - api(projects.dataforgeData) - api(projects.dataforgeIo) - } + commonMain { + api(projects.dataforgeContext) + api(projects.dataforgeData) + api(projects.dataforgeIo) + } - jvmTest{ - dependencies { - implementation(spclibs.logback.classic) - implementation(projects.dataforgeIo.dataforgeIoYaml) - } + jvmTest { + implementation(spclibs.logback.classic) + implementation(projects.dataforgeIo.dataforgeIoYaml) } } -readme{ +readme { maturity = space.kscience.gradle.Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 31ef2f9a..3734d13e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,5 +6,5 @@ kotlin.mpp.stability.nowarn=true kotlin.incremental.js.ir=true kotlin.native.ignoreDisabledTargets=true -toolsVersion=0.15.1-kotlin-1.9.21 +toolsVersion=0.15.2-kotlin-1.9.21 #kotlin.experimental.tryK2=true \ No newline at end of file