diff --git a/build.gradle.kts b/build.gradle.kts index ec6fd86..22435c0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,8 @@ +plugins{ + kotlin("jvm") version "1.4.0" apply false + kotlin("js") version "1.4.0" apply false +} + val dataforgeVersion by extra("0.1.9-dev-2") allprojects { @@ -6,6 +11,7 @@ allprojects { maven("https://dl.bintray.com/pdvrieze/maven") maven("http://maven.jzy3d.org/releases") maven("https://kotlin.bintray.com/js-externals") + maven("https://maven.pkg.github.com/altavir/kotlin-logging/") } group = "hep.dataforge" diff --git a/dataforge-device-client/build.gradle.kts b/dataforge-device-client/build.gradle.kts index 6367217..5404d11 100644 --- a/dataforge-device-client/build.gradle.kts +++ b/dataforge-device-client/build.gradle.kts @@ -1,19 +1,11 @@ plugins { - id("kscience.mpp") - id("kscience.publish") + id("ru.mipt.npm.mpp") + id("ru.mipt.npm.publish") } val ktorVersion: String by extra("1.4.0") kotlin { -// js { -// browser { -// dceTask { -// keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io") -// } -// } -// } - sourceSets { commonMain { dependencies { diff --git a/dataforge-device-core/build.gradle.kts b/dataforge-device-core/build.gradle.kts index 8c2e9de..1a47028 100644 --- a/dataforge-device-core/build.gradle.kts +++ b/dataforge-device-core/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("kscience.mpp") - id("kscience.publish") + id("ru.mipt.npm.mpp") + id("ru.mipt.npm.publish") } val dataforgeVersion: String by rootProject.extra @@ -15,7 +15,6 @@ kotlin { commonMain{ dependencies { api("hep.dataforge:dataforge-io:$dataforgeVersion") - //implementation("org.jetbrains.kotlinx:atomicfu-common:0.14.3") } } jvmMain{ diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt index ed3420e..990b42e 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/Device.kt @@ -3,7 +3,6 @@ package hep.dataforge.control.api import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET import hep.dataforge.io.Envelope import hep.dataforge.io.EnvelopeBuilder -import hep.dataforge.io.Responder import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaItem import hep.dataforge.provider.Type @@ -15,7 +14,7 @@ import kotlinx.io.Closeable * General interface describing a managed Device */ @Type(DEVICE_TARGET) -public interface Device : Responder, Closeable { +public interface Device : Closeable { /** * List of supported property descriptors */ @@ -73,7 +72,7 @@ public interface Device : Responder, Closeable { * [setProperty], [getProperty] or [execute] and not defined for a generic device. * */ - override suspend fun respond(request: Envelope): EnvelopeBuilder = error("No binary response defined") + public suspend fun respondWithData(request: Envelope): EnvelopeBuilder = error("No binary response defined") override fun close() { scope.cancel("The device is closed") diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt index 0aa2275..483d05c 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/DeviceListener.kt @@ -6,9 +6,9 @@ import hep.dataforge.meta.MetaItem * PropertyChangeListener Interface * [value] is a new value that property has after a change; null is for invalid state. */ -interface DeviceListener { - fun propertyChanged(propertyName: String, value: MetaItem<*>?) - fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {} +public interface DeviceListener { + public fun propertyChanged(propertyName: String, value: MetaItem<*>?) + public fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {} //TODO add general message listener method } \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/descriptors.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/descriptors.kt index a138ea5..a317553 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/descriptors.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/api/descriptors.kt @@ -6,17 +6,17 @@ import hep.dataforge.meta.string /** * A descriptor for property */ -class PropertyDescriptor(name: String) : Scheme() { - val name by string(name) - var info by string() +public class PropertyDescriptor(name: String) : Scheme() { + public val name: String by string(name) + public var info: String? by string() } /** * A descriptor for property */ -class ActionDescriptor(name: String) : Scheme() { - val name by string(name) - var info by string() +public class ActionDescriptor(name: String) : Scheme() { + public val name: String by string(name) + public var info: String? by string() //var descriptor by spec(ItemDescriptor) } diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/Action.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/Action.kt index 205083f..962c364 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/Action.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/Action.kt @@ -1,74 +1,10 @@ package hep.dataforge.control.base import hep.dataforge.control.api.ActionDescriptor -import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaItem -import hep.dataforge.values.Value -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty -interface Action { - val name: String - val descriptor: ActionDescriptor - suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>? -} - -private fun DeviceBase.actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?){ - notifyListeners { actionExecuted(action, argument, result) } -} - -/** - * A stand-alone action - */ -class IsolatedAction( - override val name: String, - override val descriptor: ActionDescriptor, - val callback: (action: String, argument: MetaItem<*>?, result: MetaItem<*>?) -> Unit, - val block: suspend (MetaItem<*>?) -> MetaItem<*>? -) : Action { - override suspend fun invoke(arg: MetaItem<*>?): MetaItem<*>? = block(arg).also { - callback(name, arg, it) - } -} - -class ActionDelegate( - val owner: D, - val descriptorBuilder: ActionDescriptor.() -> Unit = {}, - val block: suspend (MetaItem<*>?) -> MetaItem<*>? -) : ReadOnlyProperty { - override fun getValue(thisRef: D, property: KProperty<*>): Action { - val name = property.name - return owner.registerAction(name) { - IsolatedAction(name, ActionDescriptor(name).apply(descriptorBuilder), owner::actionExecuted, block) - } - } -} - -fun D.request( - descriptorBuilder: ActionDescriptor.() -> Unit = {}, - block: suspend (MetaItem<*>?) -> MetaItem<*>? -): ActionDelegate = ActionDelegate(this, descriptorBuilder, block) - -fun D.requestValue( - descriptorBuilder: ActionDescriptor.() -> Unit = {}, - block: suspend (MetaItem<*>?) -> Any? -): ActionDelegate = ActionDelegate(this, descriptorBuilder) { - val res = block(it) - MetaItem.ValueItem(Value.of(res)) -} - -fun D.requestMeta( - descriptorBuilder: ActionDescriptor.() -> Unit = {}, - block: suspend MetaBuilder.(MetaItem<*>?) -> Unit -): ActionDelegate = ActionDelegate(this, descriptorBuilder) { - val res = MetaBuilder().apply { block(it) } - MetaItem.NodeItem(res) -} - -fun D.action( - descriptorBuilder: ActionDescriptor.() -> Unit = {}, - block: suspend (MetaItem<*>?) -> Unit -): ActionDelegate = ActionDelegate(this, descriptorBuilder) { - block(it) - null +public interface Action { + public val name: String + public val descriptor: ActionDescriptor + public suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>? } \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt index 1647bc9..879b8ba 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/DeviceBase.kt @@ -5,14 +5,23 @@ import hep.dataforge.control.api.Device import hep.dataforge.control.api.DeviceListener import hep.dataforge.control.api.PropertyDescriptor import hep.dataforge.meta.MetaItem +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext /** * Baseline implementation of [Device] interface */ -abstract class DeviceBase : Device { - private val properties = HashMap() - private val actions = HashMap() +public abstract class DeviceBase : Device { + private val _properties = HashMap() + public val properties: Map get() = _properties + private val _actions = HashMap() + public val actions: Map get() = _actions private val listeners = ArrayList>(4) @@ -24,11 +33,11 @@ abstract class DeviceBase : Device { listeners.removeAll { it.first == owner } } - fun notifyListeners(block: DeviceListener.() -> Unit) { + internal fun notifyListeners(block: DeviceListener.() -> Unit) { listeners.forEach { it.second.block() } } - fun notifyPropertyChanged(propertyName: String) { + public fun notifyPropertyChanged(propertyName: String) { scope.launch { val value = getProperty(propertyName) notifyListeners { propertyChanged(propertyName, value) } @@ -36,41 +45,188 @@ abstract class DeviceBase : Device { } override val propertyDescriptors: Collection - get() = properties.values.map { it.descriptor } + get() = _properties.values.map { it.descriptor } override val actionDescriptors: Collection - get() = actions.values.map { it.descriptor } + get() = _actions.values.map { it.descriptor } - internal fun registerProperty(name: String, builder: () -> ReadOnlyDeviceProperty): ReadOnlyDeviceProperty { - return properties.getOrPut(name, builder) + internal fun

registerProperty(name: String, property: P) { + if (_properties.contains(name)) error("Property with name $name already registered") + _properties[name] = property } - internal fun registerMutableProperty(name: String, builder: () -> DeviceProperty): DeviceProperty { - return properties.getOrPut(name, builder) as DeviceProperty - } - - internal fun registerAction(name: String, builder: () -> Action): Action { - return actions.getOrPut(name, builder) + internal fun registerAction(name: String, action: Action) { + if (_actions.contains(name)) error("Action with name $name already registered") + _actions[name] = action } override suspend fun getProperty(propertyName: String): MetaItem<*> = - (properties[propertyName] ?: error("Property with name $propertyName not defined")).read() + (_properties[propertyName] ?: error("Property with name $propertyName not defined")).read() override suspend fun invalidateProperty(propertyName: String) { - (properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate() + (_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate() } override suspend fun setProperty(propertyName: String, value: MetaItem<*>) { - (properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write( + (_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write( value ) } override suspend fun execute(command: String, argument: MetaItem<*>?): MetaItem<*>? = - (actions[command] ?: error("Request with name $command not defined")).invoke(argument) + (_actions[command] ?: error("Request with name $command not defined")).invoke(argument) + @OptIn(ExperimentalCoroutinesApi::class) + private open inner class BasicReadOnlyDeviceProperty( + override val name: String, + default: MetaItem<*>?, + override val descriptor: PropertyDescriptor, + private val getter: suspend (before: MetaItem<*>?) -> MetaItem<*>, + ) : ReadOnlyDeviceProperty { - companion object { + override val scope: CoroutineScope get() = this@DeviceBase.scope + + private val state: MutableStateFlow?> = MutableStateFlow(default) + override val value: MetaItem<*>? get() = state.value + + override suspend fun invalidate() { + state.value = null + } + + override fun updateLogical(item: MetaItem<*>) { + state.value = item + notifyListeners { + propertyChanged(name, item) + } + } + + override suspend fun read(force: Boolean): MetaItem<*> { + //backup current value + val currentValue = value + return if (force || currentValue == null) { + val res = withContext(scope.coroutineContext) { + //all device operations should be run on device context + //TODO add error catching + getter(currentValue) + } + updateLogical(res) + res + } else { + currentValue + } + } + + override fun flow(): StateFlow?> = state + } + + /** + * Create a bound read-only property with given [getter] + */ + public fun newReadOnlyProperty( + name: String, + default: MetaItem<*>?, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (MetaItem<*>?) -> MetaItem<*>, + ): ReadOnlyDeviceProperty { + val property = BasicReadOnlyDeviceProperty( + name, + default, + PropertyDescriptor(name).apply(descriptorBuilder), + getter + ) + registerProperty(name, property) + return property + } + + @OptIn(ExperimentalCoroutinesApi::class) + private inner class BasicDeviceProperty( + name: String, + default: MetaItem<*>?, + descriptor: PropertyDescriptor, + getter: suspend (MetaItem<*>?) -> MetaItem<*>, + private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?, + ) : BasicReadOnlyDeviceProperty(name, default, descriptor, getter), DeviceProperty { + + override var value: MetaItem<*>? + get() = super.value + set(value) { + scope.launch { + if (value == null) { + invalidate() + } else { + write(value) + } + } + } + + private val writeLock = Mutex() + + override suspend fun write(item: MetaItem<*>) { + writeLock.withLock { + //fast return if value is not changed + if (item == value) return@withLock + val oldValue = value + //all device operations should be run on device context + withContext(scope.coroutineContext) { + //TODO add error catching + setter(oldValue, item)?.let { + updateLogical(it) + } + } + } + } + } + + /** + * Create a bound mutable property with given [getter] and [setter] + */ + public fun newMutableProperty( + name: String, + default: MetaItem<*>?, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (MetaItem<*>?) -> MetaItem<*>, + setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?, + ): DeviceProperty { + val property = BasicDeviceProperty( + name, + default, + PropertyDescriptor(name).apply(descriptorBuilder), + getter, + setter + ) + registerProperty(name, property) + return property + } + + /** + * A stand-alone action + */ + private inner class BasicAction( + override val name: String, + override val descriptor: ActionDescriptor, + private val block: suspend (MetaItem<*>?) -> MetaItem<*>?, + ) : Action { + override suspend fun invoke(arg: MetaItem<*>?): MetaItem<*>? = block(arg).also { + notifyListeners { + actionExecuted(name, arg, it) + } + } + } + + /** + * Create a new bound action + */ + public fun newAction( + name: String, + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + block: suspend (MetaItem<*>?) -> MetaItem<*>?, + ): Action { + val action = BasicAction(name, ActionDescriptor(name).apply(descriptorBuilder), block) + registerAction(name, action) + return action + } + + public companion object { } } diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/IsolatedDeviceProperty.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/IsolatedDeviceProperty.kt deleted file mode 100644 index 44cbd15..0000000 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/IsolatedDeviceProperty.kt +++ /dev/null @@ -1,329 +0,0 @@ -package hep.dataforge.control.base - -import hep.dataforge.control.api.PropertyDescriptor -import hep.dataforge.meta.* -import hep.dataforge.values.Null -import hep.dataforge.values.Value -import hep.dataforge.values.asValue -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -private fun DeviceBase.propertyChanged(name: String, item: MetaItem<*>?){ - notifyListeners { propertyChanged(name, item) } -} - -/** - * A stand-alone [ReadOnlyDeviceProperty] implementation not directly attached to a device - */ -@OptIn(ExperimentalCoroutinesApi::class) -public open class IsolatedReadOnlyDeviceProperty( - override val name: String, - default: MetaItem<*>?, - override val descriptor: PropertyDescriptor, - override val scope: CoroutineScope, - private val callback: (name: String, item: MetaItem<*>) -> Unit, - private val getter: suspend (before: MetaItem<*>?) -> MetaItem<*> -) : ReadOnlyDeviceProperty { - - private val state: MutableStateFlow?> = MutableStateFlow(default) - override val value: MetaItem<*>? get() = state.value - - override suspend fun invalidate() { - state.value = null - } - - override fun updateLogical(item: MetaItem<*>) { - state.value = item - callback(name, item) - } - - override suspend fun read(force: Boolean): MetaItem<*> { - //backup current value - val currentValue = value - return if (force || currentValue == null) { - val res = withContext(scope.coroutineContext) { - //all device operations should be run on device context - //TODO add error catching - getter(currentValue) - } - updateLogical(res) - res - } else { - currentValue - } - } - - override fun flow(): StateFlow?> = state -} - -public fun DeviceBase.readOnlyProperty( - name: String, - default: MetaItem<*>?, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (MetaItem<*>?) -> MetaItem<*> -): ReadOnlyDeviceProperty = registerProperty(name) { - IsolatedReadOnlyDeviceProperty( - name, - default, - PropertyDescriptor(name).apply(descriptorBuilder), - scope, - ::propertyChanged, - getter - ) -} - -private class ReadOnlyDevicePropertyDelegate( - val owner: D, - val default: MetaItem<*>?, - val descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - private val getter: suspend (MetaItem<*>?) -> MetaItem<*> -) : ReadOnlyProperty { - - override fun getValue(thisRef: D, property: KProperty<*>): ReadOnlyDeviceProperty { - val name = property.name - - return owner.registerProperty(name) { - @OptIn(ExperimentalCoroutinesApi::class) - IsolatedReadOnlyDeviceProperty( - name, - default, - PropertyDescriptor(name).apply(descriptorBuilder), - owner.scope, - owner::propertyChanged, - getter - ) - } - } -} - -public fun D.reading( - default: MetaItem<*>? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (MetaItem<*>?) -> MetaItem<*> -): ReadOnlyProperty = ReadOnlyDevicePropertyDelegate( - this, - default, - descriptorBuilder, - getter -) - -public fun D.readingValue( - default: Value? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend () -> Any? -): ReadOnlyProperty = ReadOnlyDevicePropertyDelegate( - this, - default?.let { MetaItem.ValueItem(it) }, - descriptorBuilder, - getter = { MetaItem.ValueItem(Value.of(getter())) } -) - -public fun D.readingNumber( - default: Number? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend () -> Number -): ReadOnlyProperty = ReadOnlyDevicePropertyDelegate( - this, - default?.let { MetaItem.ValueItem(it.asValue()) }, - descriptorBuilder, - getter = { - val number = getter() - MetaItem.ValueItem(number.asValue()) - } -) - -public fun D.readingString( - default: Number? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend () -> String -): ReadOnlyProperty = ReadOnlyDevicePropertyDelegate( - this, - default?.let { MetaItem.ValueItem(it.asValue()) }, - descriptorBuilder, - getter = { - val number = getter() - MetaItem.ValueItem(number.asValue()) - } -) - -public fun D.readingMeta( - default: Meta? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend MetaBuilder.() -> Unit -): ReadOnlyProperty = ReadOnlyDevicePropertyDelegate( - this, - default?.let { MetaItem.NodeItem(it) }, - descriptorBuilder, - getter = { - MetaItem.NodeItem(MetaBuilder().apply { getter() }) - } -) - -@OptIn(ExperimentalCoroutinesApi::class) -public class IsolatedDeviceProperty( - name: String, - default: MetaItem<*>?, - descriptor: PropertyDescriptor, - scope: CoroutineScope, - updateCallback: (name: String, item: MetaItem<*>?) -> Unit, - getter: suspend (MetaItem<*>?) -> MetaItem<*>, - private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? -) : IsolatedReadOnlyDeviceProperty(name, default, descriptor, scope, updateCallback, getter), DeviceProperty { - - override var value: MetaItem<*>? - get() = super.value - set(value) { - scope.launch { - if (value == null) { - invalidate() - } else { - write(value) - } - } - } - - private val writeLock = Mutex() - - override suspend fun write(item: MetaItem<*>) { - writeLock.withLock { - //fast return if value is not changed - if (item == value) return@withLock - val oldValue = value - //all device operations should be run on device context - withContext(scope.coroutineContext) { - //TODO add error catching - setter(oldValue, item)?.let { - updateLogical(it) - } - } - } - } -} - -public fun DeviceBase.mutableProperty( - name: String, - default: MetaItem<*>?, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (MetaItem<*>?) -> MetaItem<*>, - setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? -): DeviceProperty = registerMutableProperty(name) { - IsolatedDeviceProperty( - name, - default, - PropertyDescriptor(name).apply(descriptorBuilder), - scope, - ::propertyChanged, - getter, - setter - ) -} - -private class DevicePropertyDelegate( - val owner: D, - val default: MetaItem<*>?, - val descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - private val getter: suspend (MetaItem<*>?) -> MetaItem<*>, - private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? -) : ReadOnlyProperty { - - override fun getValue(thisRef: D, property: KProperty<*>): IsolatedDeviceProperty { - val name = property.name - return owner.registerMutableProperty(name) { - @OptIn(ExperimentalCoroutinesApi::class) - IsolatedDeviceProperty( - name, - default, - PropertyDescriptor(name).apply(descriptorBuilder), - owner.scope, - owner::propertyChanged, - getter, - setter - ) - } as IsolatedDeviceProperty - } -} - -public fun D.writing( - default: MetaItem<*>? = null, - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (MetaItem<*>?) -> MetaItem<*>, - setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? -): ReadOnlyProperty = DevicePropertyDelegate( - this, - default, - descriptorBuilder, - getter, - setter -) - -public fun D.writingVirtual( - default: MetaItem<*>, - descriptorBuilder: PropertyDescriptor.() -> Unit = {} -): ReadOnlyProperty = writing( - default, - descriptorBuilder, - getter = { it ?: default }, - setter = { _, newItem -> newItem } -) - -public fun D.writingVirtual( - default: Value, - descriptorBuilder: PropertyDescriptor.() -> Unit = {} -): ReadOnlyProperty = writing( - MetaItem.ValueItem(default), - descriptorBuilder, - getter = { it ?: MetaItem.ValueItem(default) }, - setter = { _, newItem -> newItem } -) - -public fun D.writingDouble( - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (Double) -> Double, - setter: suspend (oldValue: Double?, newValue: Double) -> Double? -): ReadOnlyProperty { - val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { - MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue()) - } - - val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue -> - setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem() - } - - return DevicePropertyDelegate( - this, - MetaItem.ValueItem(Double.NaN.asValue()), - descriptorBuilder, - innerGetter, - innerSetter - ) -} - -public fun D.writingBoolean( - descriptorBuilder: PropertyDescriptor.() -> Unit = {}, - getter: suspend (Boolean?) -> Boolean, - setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean? -): ReadOnlyProperty { - val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { - MetaItem.ValueItem(getter(it.boolean).asValue()) - } - - val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue -> - setter(oldValue.boolean, newValue.boolean?: error("Can't convert $newValue to boolean"))?.asValue()?.asMetaItem() - } - - return DevicePropertyDelegate( - this, - MetaItem.ValueItem(Null), - descriptorBuilder, - innerGetter, - innerSetter - ) -} \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/actionDelegates.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/actionDelegates.kt new file mode 100644 index 0000000..7b34c42 --- /dev/null +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/actionDelegates.kt @@ -0,0 +1,59 @@ +package hep.dataforge.control.base + +import hep.dataforge.control.api.ActionDescriptor +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.MetaItem +import hep.dataforge.values.Value +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + + +private fun D.provideAction(): ReadOnlyProperty = + ReadOnlyProperty { _: D, property: KProperty<*> -> + val name = property.name + return@ReadOnlyProperty actions[name]!! + } + +public typealias ActionDelegate = ReadOnlyProperty + +private class ActionProvider( + val owner: D, + val descriptorBuilder: ActionDescriptor.() -> Unit = {}, + val block: suspend (MetaItem<*>?) -> MetaItem<*>?, +) : PropertyDelegateProvider { + override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate { + val name = property.name + owner.newAction(name, descriptorBuilder, block) + return owner.provideAction() + } +} + +public fun DeviceBase.requesting( + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + block: suspend (MetaItem<*>?) -> MetaItem<*>?, +): PropertyDelegateProvider = ActionProvider(this, descriptorBuilder, block) + +public fun D.requestingValue( + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + block: suspend (MetaItem<*>?) -> Any?, +): PropertyDelegateProvider = ActionProvider(this, descriptorBuilder) { + val res = block(it) + MetaItem.ValueItem(Value.of(res)) +} + +public fun D.requestingMeta( + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + block: suspend MetaBuilder.(MetaItem<*>?) -> Unit, +): PropertyDelegateProvider = ActionProvider(this, descriptorBuilder) { + val res = MetaBuilder().apply { block(it) } + MetaItem.NodeItem(res) +} + +public fun DeviceBase.acting( + descriptorBuilder: ActionDescriptor.() -> Unit = {}, + block: suspend (MetaItem<*>?) -> Unit, +): PropertyDelegateProvider = ActionProvider(this, descriptorBuilder) { + block(it) + null +} \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/devicePropertyDelegates.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/devicePropertyDelegates.kt new file mode 100644 index 0000000..3cf044e --- /dev/null +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/base/devicePropertyDelegates.kt @@ -0,0 +1,196 @@ +package hep.dataforge.control.base + +import hep.dataforge.control.api.PropertyDescriptor +import hep.dataforge.meta.* +import hep.dataforge.values.Null +import hep.dataforge.values.Value +import hep.dataforge.values.asValue +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +private fun D.provideProperty(): ReadOnlyProperty = + ReadOnlyProperty { _: D, property: KProperty<*> -> + val name = property.name + return@ReadOnlyProperty properties[name]!! + } + +public typealias ReadOnlyPropertyDelegate = ReadOnlyProperty + +private class ReadOnlyDevicePropertyProvider( + val owner: D, + val default: MetaItem<*>?, + val descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + private val getter: suspend (MetaItem<*>?) -> MetaItem<*>, +) : PropertyDelegateProvider { + + override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate { + val name = property.name + owner.newReadOnlyProperty(name, default, descriptorBuilder, getter) + return owner.provideProperty() + } +} + +public fun DeviceBase.reading( + default: MetaItem<*>? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (MetaItem<*>?) -> MetaItem<*>, +): PropertyDelegateProvider = ReadOnlyDevicePropertyProvider( + this, + default, + descriptorBuilder, + getter +) + +public fun DeviceBase.readingValue( + default: Value? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend () -> Any?, +): PropertyDelegateProvider = ReadOnlyDevicePropertyProvider( + this, + default?.let { MetaItem.ValueItem(it) }, + descriptorBuilder, + getter = { MetaItem.ValueItem(Value.of(getter())) } +) + +public fun DeviceBase.readingNumber( + default: Number? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend () -> Number, +): PropertyDelegateProvider = ReadOnlyDevicePropertyProvider( + this, + default?.let { MetaItem.ValueItem(it.asValue()) }, + descriptorBuilder, + getter = { + val number = getter() + MetaItem.ValueItem(number.asValue()) + } +) + +public fun DeviceBase.readingString( + default: Number? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend () -> String, +): PropertyDelegateProvider = ReadOnlyDevicePropertyProvider( + this, + default?.let { MetaItem.ValueItem(it.asValue()) }, + descriptorBuilder, + getter = { + val number = getter() + MetaItem.ValueItem(number.asValue()) + } +) + +public fun DeviceBase.readingMeta( + default: Meta? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend MetaBuilder.() -> Unit, +): PropertyDelegateProvider = ReadOnlyDevicePropertyProvider( + this, + default?.let { MetaItem.NodeItem(it) }, + descriptorBuilder, + getter = { + MetaItem.NodeItem(MetaBuilder().apply { getter() }) + } +) + +private fun DeviceBase.provideMutableProperty(): ReadOnlyProperty = + ReadOnlyProperty { _: DeviceBase, property: KProperty<*> -> + val name = property.name + return@ReadOnlyProperty properties[name] as DeviceProperty + } + +public typealias PropertyDelegate = ReadOnlyProperty + +private class DevicePropertyProvider( + val owner: D, + val default: MetaItem<*>?, + val descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + private val getter: suspend (MetaItem<*>?) -> MetaItem<*>, + private val setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?, +) : PropertyDelegateProvider { + + override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate { + val name = property.name + owner.newMutableProperty(name, default, descriptorBuilder, getter, setter) + return owner.provideMutableProperty() + } +} + +public fun DeviceBase.writing( + default: MetaItem<*>? = null, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (MetaItem<*>?) -> MetaItem<*>, + setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?, +): PropertyDelegateProvider = DevicePropertyProvider( + this, + default, + descriptorBuilder, + getter, + setter +) + +public fun DeviceBase.writingVirtual( + default: MetaItem<*>, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, +): PropertyDelegateProvider = writing( + default, + descriptorBuilder, + getter = { it ?: default }, + setter = { _, newItem -> newItem } +) + +public fun DeviceBase.writingVirtual( + default: Value, + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, +): PropertyDelegateProvider = writing( + MetaItem.ValueItem(default), + descriptorBuilder, + getter = { it ?: MetaItem.ValueItem(default) }, + setter = { _, newItem -> newItem } +) + +public fun D.writingDouble( + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (Double) -> Double, + setter: suspend (oldValue: Double?, newValue: Double) -> Double?, +): PropertyDelegateProvider { + val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { + MetaItem.ValueItem(getter(it.double ?: Double.NaN).asValue()) + } + + val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue -> + setter(oldValue.double, newValue.double ?: Double.NaN)?.asMetaItem() + } + + return DevicePropertyProvider( + this, + MetaItem.ValueItem(Double.NaN.asValue()), + descriptorBuilder, + innerGetter, + innerSetter + ) +} + +public fun D.writingBoolean( + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, + getter: suspend (Boolean?) -> Boolean, + setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?, +): PropertyDelegateProvider { + val innerGetter: suspend (MetaItem<*>?) -> MetaItem<*> = { + MetaItem.ValueItem(getter(it.boolean).asValue()) + } + + val innerSetter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>? = { oldValue, newValue -> + setter(oldValue.boolean, newValue.boolean ?: error("Can't convert $newValue to boolean"))?.asValue() + ?.asMetaItem() + } + + return DevicePropertyProvider( + this, + MetaItem.ValueItem(Null), + descriptorBuilder, + innerGetter, + innerSetter + ) +} \ No newline at end of file diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt index 6e4465f..6607c5c 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/DeviceController.kt @@ -78,7 +78,7 @@ class DeviceController( } else if (target != null && target != deviceTarget) { error("Wrong target name $deviceTarget expected but $target found") } else { - val response = device.respond(request).apply { + val response = device.respondWithData(request).apply { meta { "target" put request.meta["source"].string "source" put deviceTarget diff --git a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegates.kt b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegates.kt index 122c917..7f1b41c 100644 --- a/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegates.kt +++ b/dataforge-device-core/src/commonMain/kotlin/hep/dataforge/control/controllers/delegates.kt @@ -8,6 +8,7 @@ import hep.dataforge.values.Null import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty +import kotlin.time.Duration operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> = value ?: MetaItem.ValueItem(Null) @@ -17,10 +18,8 @@ operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, valu } fun ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter): ReadOnlyProperty { - return object : ReadOnlyProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - return this@convert.getValue(thisRef, property).let { metaConverter.itemToObject(it) } - } + return ReadOnlyProperty { thisRef, property -> + getValue(thisRef, property).let { metaConverter.itemToObject(it) } } } @@ -43,4 +42,7 @@ fun ReadOnlyDeviceProperty.int() = convert(MetaConverter.int) fun DeviceProperty.int() = convert(MetaConverter.int) fun ReadOnlyDeviceProperty.string() = convert(MetaConverter.string) -fun DeviceProperty.string() = convert(MetaConverter.string) \ No newline at end of file +fun DeviceProperty.string() = convert(MetaConverter.string) + +fun ReadOnlyDeviceProperty.duration(): ReadOnlyProperty = TODO() +fun DeviceProperty.duration(): ReadWriteProperty = TODO() \ No newline at end of file diff --git a/dataforge-device-serial/build.gradle.kts b/dataforge-device-serial/build.gradle.kts index 6d033ae..a5036d4 100644 --- a/dataforge-device-serial/build.gradle.kts +++ b/dataforge-device-serial/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("kscience.jvm") - id("kscience.publish") + id("ru.mipt.npm.jvm") + id("ru.mipt.npm.publish") } dependencies{ diff --git a/dataforge-device-server/build.gradle.kts b/dataforge-device-server/build.gradle.kts index 7e0c1ce..8f7428e 100644 --- a/dataforge-device-server/build.gradle.kts +++ b/dataforge-device-server/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("kscience.jvm") - id("kscience.publish") + id("ru.mipt.npm.jvm") + id("ru.mipt.npm.publish") } kscience { @@ -8,7 +8,7 @@ kscience { } val dataforgeVersion: String by rootProject.extra -val ktorVersion: String by extra("1.3.2") +val ktorVersion: String by extra("1.4.0") dependencies{ implementation(project(":dataforge-device-core")) diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index d104cf4..a7c39a4 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -6,6 +6,7 @@ plugins { repositories{ + mavenLocal() jcenter() maven("https://kotlin.bintray.com/kotlinx") maven("https://dl.bintray.com/kotlin/kotlin-eap") @@ -21,7 +22,7 @@ dependencies{ implementation(project(":dataforge-device-client")) implementation("no.tornado:tornadofx:1.7.20") implementation(kotlin("stdlib-jdk8")) - implementation("kscience.plotlykt:plotlykt-server:0.2.0") + implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2") } tasks.withType().configureEach { diff --git a/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt b/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt index 7ff95d3..220d0a9 100644 --- a/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt +++ b/demo/src/main/kotlin/hep/dataforge/control/demo/DemoDevice.kt @@ -50,7 +50,7 @@ class DemoDevice(parentScope: CoroutineScope) : DeviceBase() { } - val resetScale: Action by action { + val resetScale: Action by acting { timeScaleValue = 5000.0 sinScaleValue = 1.0 cosScaleValue = 1.0 diff --git a/docs/schemes/direct-vs-loop.vsdx b/docs/schemes/direct-vs-loop.vsdx new file mode 100644 index 0000000..812a47e Binary files /dev/null and b/docs/schemes/direct-vs-loop.vsdx differ diff --git a/motors/build.gradle.kts b/motors/build.gradle.kts index cb0dbd8..4b747ef 100644 --- a/motors/build.gradle.kts +++ b/motors/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("kscience.jvm") - id("kscience.publish") + id("ru.mipt.npm.jvm") + id("ru.mipt.npm.publish") } //TODO to be moved to a separate project diff --git a/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt b/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt index 56998ea..af5d831 100644 --- a/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt +++ b/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt @@ -1,11 +1,14 @@ package ru.mipt.npm.devices.pimotionmaster +import hep.dataforge.control.api.DeviceHub import hep.dataforge.control.base.* +import hep.dataforge.control.controllers.duration import hep.dataforge.control.ports.Port import hep.dataforge.control.ports.PortProxy import hep.dataforge.control.ports.send import hep.dataforge.control.ports.withDelimiter import hep.dataforge.meta.MetaItem +import hep.dataforge.names.NameToken import hep.dataforge.values.Null import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -14,11 +17,15 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.toList import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration + public class PiMotionMasterDevice( parentScope: CoroutineScope, + axes: List, private val portFactory: suspend (MetaItem<*>?) -> Port, -) : DeviceBase() { +) : DeviceBase(), DeviceHub { override val scope: CoroutineScope = CoroutineScope( parentScope.coroutineContext + Job(parentScope.coroutineContext[Job]) @@ -28,11 +35,17 @@ public class PiMotionMasterDevice( info = "The port for TCP connector" } + public val timeout: DeviceProperty by writingVirtual(Null) { + info = "Timeout" + } + + public var timeoutValue: Duration by timeout.duration() + private val connector = PortProxy { portFactory(port.value) } private val mutex = Mutex() - private suspend fun sendCommand(command: String, vararg arguments: String) { + private suspend fun sendCommandInternal(command: String, vararg arguments: String) { val joinedArguments = if (arguments.isEmpty()) { "" } else { @@ -46,9 +59,11 @@ public class PiMotionMasterDevice( * Send a synchronous request and receive a list of lines as a response */ private suspend fun request(command: String, vararg arguments: String): List = mutex.withLock { - sendCommand(command, *arguments) - val phrases = connector.receiving().withDelimiter("\n") - return@withLock phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first() + withTimeout(timeoutValue) { + sendCommandInternal(command, *arguments) + val phrases = connector.receiving().withDelimiter("\n") + phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first() + } } private suspend fun requestAndParse(command: String, vararg arguments: String): Map = buildMap { @@ -63,11 +78,13 @@ public class PiMotionMasterDevice( */ private suspend fun send(command: String, vararg arguments: String) { mutex.withLock { - sendCommand(command, *arguments) + withTimeout(timeoutValue) { + sendCommandInternal(command, *arguments) + } } } - public val initialize: Action by action { + public val initialize: Action by acting { send("INI") } @@ -79,32 +96,37 @@ public class PiMotionMasterDevice( override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope public val enabled: DeviceProperty by writingBoolean( getter = { - val result = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull() - ?: error("Malformed response. Should include integer value for $axisId") - result != 0 + val eax = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull() + ?: error("Malformed EAX response. Should include integer value for $axisId") + eax != 0 }, - setter = { oldValue, newValue -> - val value = if(newValue){ + setter = { _, newValue -> + val value = if (newValue) { "1" } else { "0" } send("EAX", axisId, value) - oldValue + newValue } ) - public val halt: Action by action { + public val halt: Action by acting { send("HLT", axisId) } + + public val targetPosition: DeviceProperty by writingDouble( + getter = { + requestAndParse("MOV?", axisId)[axisId]?.toDoubleOrNull() + ?: error("Malformed MOV response. Should include float value for $axisId") + }, + setter = { _, newValue -> + send("MOV", axisId, newValue.toString()) + newValue + } + ) } - init { - //list everything here to ensure it is initialized - initialize - firmwareVersion - - } - + override val devices: Map = axes.associate { NameToken(it) to Axis(it) } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 325f4d6..40fd8a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { val kotlinVersion = "1.4.0" - val toolsVersion = "0.6.0-dev-1" + val toolsVersion = "0.6.0-dev-4" repositories { mavenLocal() @@ -9,25 +9,17 @@ pluginManagement { maven("https://kotlin.bintray.com/kotlinx") maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/mipt-npm/dataforge") - maven("https://dl.bintray.com/mipt-npm/scientifik") + maven("https://dl.bintray.com/mipt-npm/kscience") maven("https://dl.bintray.com/mipt-npm/dev") } plugins { + id("ru.mipt.npm.mpp") version toolsVersion + id("ru.mipt.npm.jvm") version toolsVersion + id("ru.mipt.npm.js") version toolsVersion + id("ru.mipt.npm.publish") version toolsVersion kotlin("jvm") version kotlinVersion - id("scientifik.mpp") version toolsVersion - id("scientifik.jvm") version toolsVersion - id("scientifik.js") version toolsVersion - id("scientifik.publish") version toolsVersion - } - - resolutionStrategy { - eachPlugin { - when (requested.id.id) { - "kscience.publish", "kscience.mpp", "kscience.jvm", "kscience.js" -> useModule("ru.mipt.npm:gradle-tools:${toolsVersion}") - "kotlinx-atomicfu" -> useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") - } - } + kotlin("js") version kotlinVersion } }