From 3f54eee578c9f85d36a713b0e603d78d0fa52fd5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Oct 2021 14:32:32 +0300 Subject: [PATCH] Workaround for https://youtrack.jetbrains.com/issue/KT-48988. Smart building of child contexts --- build.gradle.kts | 2 +- .../kscience/dataforge/context/Context.kt | 17 ++++----- .../dataforge/context/ContextBuilder.kt | 14 ++++---- .../kscience/dataforge/context/Global.kt | 6 ++-- .../kscience/dataforge/context/LogManager.kt | 2 +- .../dataforge/context/PluginManager.kt | 3 +- .../kscience/dataforge/context/ContextTest.kt | 3 +- .../kscience/dataforge/context/loggingJs.kt | 2 +- .../kscience/dataforge/context/loggingJvm.kt | 2 +- .../dataforge/context/loggingNative.kt | 2 +- .../space/kscience/dataforge/data/DataSet.kt | 4 +-- .../space/kscience/dataforge/data/select.kt | 36 +++++++++++++------ .../space/kscience/dataforge/io/IOPlugin.kt | 18 +++++----- .../dataforge/io/TaggedEnvelopeFormat.kt | 2 +- .../dataforge/io/TaglessEnvelopeFormat.kt | 6 ++-- .../dataforge/scripting/BuildersKtTest.kt | 4 +-- .../dataforge/workspace/WorkspaceBuilder.kt | 3 +- gradle.properties | 4 +-- 18 files changed, 72 insertions(+), 58 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fe2e7f58..42505cb9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { allprojects { group = "space.kscience" - version = "0.5.1" + version = "0.5.2-dev-1" repositories{ mavenCentral() } diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt index 97efc76a..427d8611 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import space.kscience.dataforge.meta.* +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.Named import space.kscience.dataforge.names.Name import space.kscience.dataforge.provider.Provider @@ -71,16 +72,16 @@ public open class Context internal constructor( private val childrenContexts = HashMap() /** - * Build and register a child context + * Get and validate existing context or build and register a new child context. + * @param name the relative (tail) name of the new context. If null, uses context hash code as a marker. */ + @OptIn(DFExperimental::class) @Synchronized - public fun buildContext(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context { - val newContext = ContextBuilder(this) - .apply { name?.let { name(it) } } - .apply(block) - .build() - childrenContexts[newContext.name] = newContext - return newContext + public fun buildContext(name: Name? = null, block: ContextBuilder.() -> Unit = {}): Context { + val existing = name?.let { childrenContexts[name] } + return existing?.modify(block)?: ContextBuilder(this, name).apply(block).build().also { + childrenContexts[it.name] = it + } } /** diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt index 0de5fb7a..80329176 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt @@ -20,7 +20,7 @@ import kotlin.collections.set @DFBuilder public class ContextBuilder internal constructor( private val parent: Context, - public var name: Name? = null, + public val name: Name? = null, meta: Meta = Meta.EMPTY, ) { internal val factories = HashMap, Meta>() @@ -30,10 +30,6 @@ public class ContextBuilder internal constructor( meta.action() } - public fun name(string: String) { - this.name = Name.parse(string) - } - @OptIn(DFExperimental::class) private fun findPluginFactory(tag: PluginTag): PluginFactory<*> = parent.gatherInSequence>(PluginFactory.TYPE).values @@ -95,19 +91,21 @@ public class ContextBuilder internal constructor( } /** - * Check if current context contains all plugins required by the builder and return it it does or forks to a new context + * Check if current context contains all plugins required by the builder and return it does or forks to a new context * if it does not. */ -public fun Context.withEnv(block: ContextBuilder.() -> Unit): Context { +@DFExperimental +public fun Context.modify(block: ContextBuilder.() -> Unit): Context { fun Context.contains(factory: PluginFactory<*>, meta: Meta): Boolean { val loaded = plugins[factory.tag] ?: return false return loaded.meta == meta } - val builder = ContextBuilder(this, name + "env", properties).apply(block) + val builder = ContextBuilder(this, name + "mod", properties).apply(block) val requiresFork = builder.factories.any { (factory, meta) -> !contains(factory, meta) } || ((properties as Meta) == builder.meta) + return if (requiresFork) builder.build() else this } \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Global.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Global.kt index 9ace5800..05bce836 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Global.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Global.kt @@ -3,11 +3,13 @@ package space.kscience.dataforge.context import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Job import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.Name.Companion.parse import space.kscience.dataforge.names.asName import kotlin.coroutines.CoroutineContext import kotlin.native.concurrent.ThreadLocal -internal expect val globalLoggerFactory: PluginFactory +internal expect fun getGlobalLoggerFactory(): PluginFactory /** * A global root context. Closing [Global] terminates the framework. @@ -20,4 +22,4 @@ private object GlobalContext : Context("GLOBAL".asName(), null, emptySet(), Meta public val Global: Context get() = GlobalContext public fun Context(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context = - Global.buildContext(name, block) \ No newline at end of file + Global.buildContext(name?.let(Name::parse), block) \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/LogManager.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/LogManager.kt index d3efed6f..84a2423d 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/LogManager.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/LogManager.kt @@ -75,7 +75,7 @@ public class DefaultLogManager : AbstractPlugin(), LogManager { */ public val Context.logger: LogManager get() = plugins.find(inherit = true) { it is LogManager } as? LogManager - ?: globalLoggerFactory(context = Global).apply { attach(Global) } + ?: getGlobalLoggerFactory()(context = Global).apply { attach(Global) } /** * The named proxy logger for a context member diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginManager.kt index 81d455c7..d8d7b602 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginManager.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginManager.kt @@ -1,6 +1,7 @@ package space.kscience.dataforge.context import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.plus import kotlin.reflect.KClass @@ -84,7 +85,7 @@ public inline fun Context.fetch(factory: PluginFactory, val existing = plugins[factory] return if (existing != null && existing.meta == meta) existing else { - buildContext { + buildContext(name = this@fetch.name + factory.tag.name) { plugin(factory, meta) }.plugins[factory]!! } diff --git a/dataforge-context/src/commonTest/kotlin/space/kscience/dataforge/context/ContextTest.kt b/dataforge-context/src/commonTest/kotlin/space/kscience/dataforge/context/ContextTest.kt index 8e39eb3f..c00921cd 100644 --- a/dataforge-context/src/commonTest/kotlin/space/kscience/dataforge/context/ContextTest.kt +++ b/dataforge-context/src/commonTest/kotlin/space/kscience/dataforge/context/ContextTest.kt @@ -20,8 +20,7 @@ class ContextTest { @Test fun testPluginManager() { - val context = Global.buildContext { - name("test") + val context = Context("test") { plugin(DummyPlugin()) } val members = context.gather("test") diff --git a/dataforge-context/src/jsMain/kotlin/space/kscience/dataforge/context/loggingJs.kt b/dataforge-context/src/jsMain/kotlin/space/kscience/dataforge/context/loggingJs.kt index 406929ca..0aae274a 100644 --- a/dataforge-context/src/jsMain/kotlin/space/kscience/dataforge/context/loggingJs.kt +++ b/dataforge-context/src/jsMain/kotlin/space/kscience/dataforge/context/loggingJs.kt @@ -29,4 +29,4 @@ public class ConsoleLogManager : AbstractPlugin(), LogManager { } } -internal actual val globalLoggerFactory: PluginFactory = ConsoleLogManager +internal actual fun getGlobalLoggerFactory(): PluginFactory = ConsoleLogManager diff --git a/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/context/loggingJvm.kt b/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/context/loggingJvm.kt index 05763e00..445e04c9 100644 --- a/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/context/loggingJvm.kt +++ b/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/context/loggingJvm.kt @@ -31,4 +31,4 @@ public class SlfLogManager : AbstractPlugin(), LogManager { } } -internal actual val globalLoggerFactory: PluginFactory = SlfLogManager +internal actual fun getGlobalLoggerFactory(): PluginFactory = SlfLogManager diff --git a/dataforge-context/src/nativeMain/kotlin/space/kscience/dataforge/context/loggingNative.kt b/dataforge-context/src/nativeMain/kotlin/space/kscience/dataforge/context/loggingNative.kt index 62512132..ff4155f0 100644 --- a/dataforge-context/src/nativeMain/kotlin/space/kscience/dataforge/context/loggingNative.kt +++ b/dataforge-context/src/nativeMain/kotlin/space/kscience/dataforge/context/loggingNative.kt @@ -1,4 +1,4 @@ package space.kscience.dataforge.context -internal actual val globalLoggerFactory: PluginFactory = DefaultLogManager +internal actual fun getGlobalLoggerFactory(): PluginFactory = DefaultLogManager diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt index 305f50f8..d4ce7990 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt @@ -32,7 +32,7 @@ public interface DataSet { */ public suspend fun listTop(prefix: Name = Name.EMPTY): List = flow().map { it.name }.filter { it.startsWith(prefix) && (it.length == prefix.length + 1) }.toList() - // By default traverses the whole tree. Could be optimized in descendants + // By default, traverses the whole tree. Could be optimized in descendants public companion object { public val META_KEY: Name = "@meta".asName() @@ -43,7 +43,7 @@ public interface DataSet { public val EMPTY: DataSet = object : DataSet { override val dataType: KType = TYPE_OF_NOTHING - private val nothing: Nothing get() = error("this is nothing") + //private val nothing: Nothing get() = error("this is nothing") override fun flow(): Flow> = emptyFlow() diff --git a/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/select.kt b/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/select.kt index 2cbbc1c1..5b09468d 100644 --- a/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/select.kt +++ b/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/select.kt @@ -3,6 +3,7 @@ package space.kscience.dataforge.data import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.matches @@ -26,40 +27,55 @@ private fun Data<*>.castOrNull(type: KType): Data? = /** * Select all data matching given type and filters. Does not modify paths + * + * @param namePattern a name match patter according to [Name.matches] + * @param filter addition filtering condition based on item name and meta. By default, accepts all */ @OptIn(DFExperimental::class) -@PublishedApi -internal fun DataSet<*>.select( +public fun DataSet<*>.select( type: KType, namePattern: Name? = null, + filter: (name: Name, meta: Meta) -> Boolean = { _, _ -> true } ): ActiveDataSet = object : ActiveDataSet { override val dataType = type - override fun flow(): Flow> = this@select.flow().filter { datum -> - datum.type.isSubtypeOf(type) && (namePattern == null || datum.name.matches(namePattern)) + private fun checkDatum(name: Name, datum: Data<*>): Boolean = datum.type.isSubtypeOf(type) + && (namePattern == null || name.matches(namePattern)) + && filter(name, datum.meta) + + override fun flow(): Flow> = this@select.flow().filter { + checkDatum(it.name, it.data) }.map { @Suppress("UNCHECKED_CAST") it as NamedData } - override suspend fun getData(name: Name): Data? = this@select.getData(name)?.castOrNull(type) + override suspend fun getData(name: Name): Data? = this@select.getData(name)?.let { datum -> + if (checkDatum(name, datum)) datum.castOrNull(type) else null + } override val updates: Flow = this@select.updates.filter { - val datum = this@select.getData(it) - datum?.type?.isSubtypeOf(type) ?: false + val datum = this@select.getData(it) ?: return@filter false + checkDatum(it, datum) } } /** * Select a single datum of the appropriate type */ -public inline fun DataSet<*>.select(namePattern: Name? = null): DataSet = - select(typeOf(), namePattern) +public inline fun DataSet<*>.select( + namePattern: Name? = null, + noinline filter: (name: Name, meta: Meta) -> Boolean = { _, _ -> true } +): DataSet = select(typeOf(), namePattern, filter) +/** + * Select a single datum if it is present and of given [type] + */ public suspend fun DataSet<*>.selectOne(type: KType, name: Name): NamedData? = getData(name)?.castOrNull(type)?.named(name) -public suspend inline fun DataSet<*>.selectOne(name: Name): NamedData? = selectOne(typeOf(), name) +public suspend inline fun DataSet<*>.selectOne(name: Name): NamedData? = + selectOne(typeOf(), name) public suspend inline fun DataSet<*>.selectOne(name: String): NamedData? = selectOne(typeOf(), Name.parse(name)) \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt index d7ed301f..66281f19 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt @@ -10,8 +10,7 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name - -import kotlin.native.concurrent.ThreadLocal +import space.kscience.dataforge.names.asName import kotlin.reflect.KClass public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { @@ -75,10 +74,11 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { } } -@ThreadLocal -internal val ioContext = Global.withEnv { - name("IO") - plugin(IOPlugin) -} - -public val Context.io: IOPlugin get() = (if (this == Global) ioContext else this).fetch(IOPlugin) \ No newline at end of file +public val Context.io: IOPlugin + get() = if (this == Global) { + Global.buildContext("IO".asName()) { + plugin(IOPlugin) + }.fetch(IOPlugin) + } else { + fetch(IOPlugin) + } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaggedEnvelopeFormat.kt index 8347118c..96c62db2 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaggedEnvelopeFormat.kt @@ -161,7 +161,7 @@ public class TaggedEnvelopeFormat( } } - private val default by lazy { invoke(context = ioContext) } + private val default by lazy { invoke() } override fun readPartial(input: Input): PartialEnvelope = default.run { readPartial(input) } diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaglessEnvelopeFormat.kt index e1a55607..bc8031d7 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/TaglessEnvelopeFormat.kt @@ -193,11 +193,9 @@ public class TaglessEnvelopeFormat( override val name: Name = TAGLESS_ENVELOPE_TYPE.asName() - override fun invoke(meta: Meta, context: Context): EnvelopeFormat { - return TaglessEnvelopeFormat(context.io, meta) - } + override fun invoke(meta: Meta, context: Context): EnvelopeFormat = TaglessEnvelopeFormat(context.io, meta) - private val default by lazy { invoke(context = ioContext) } + private val default by lazy { invoke() } override fun readPartial(input: Input): PartialEnvelope = default.run { readPartial(input) } diff --git a/dataforge-scripting/src/jvmTest/kotlin/space/kscience/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/space/kscience/dataforge/scripting/BuildersKtTest.kt index 36ad16fb..345faf14 100644 --- a/dataforge-scripting/src/jvmTest/kotlin/space/kscience/dataforge/scripting/BuildersKtTest.kt +++ b/dataforge-scripting/src/jvmTest/kotlin/space/kscience/dataforge/scripting/BuildersKtTest.kt @@ -15,7 +15,7 @@ class BuildersKtTest { Workspace(Global){ println("I am working") - context { name("test") } + //context { name("test") } target("testTarget") { "a" put 12 @@ -28,8 +28,6 @@ class BuildersKtTest { val script = """ println("I am working") - context{ name("test") } - target("testTarget"){ "a" put 12 } diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt index 72a35da7..2d5715de 100644 --- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt @@ -14,6 +14,7 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadOnlyProperty @@ -62,7 +63,7 @@ public class WorkspaceBuilder(private val parentContext: Context = Global) : Tas * Define a context for the workspace */ public fun context(block: ContextBuilder.() -> Unit = {}) { - this.context = parentContext.buildContext("workspace", block) + this.context = parentContext.buildContext("workspace".asName(), block) } /** diff --git a/gradle.properties b/gradle.properties index 2bae1b5e..5c9b6463 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,6 @@ org.gradle.parallel=true kotlin.code.style=official kotlin.parallel.tasks.in.project=true -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false +#kotlin.mpp.enableGranularSourceSetsMetadata=true +#kotlin.native.enableDependencyPropagation=false kotlin.mpp.stability.nowarn=true