Unisolate properties
This commit is contained in:
parent
10bb85ac83
commit
4b5bc40a4f
@ -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")
|
val dataforgeVersion by extra("0.1.9-dev-2")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@ -6,6 +11,7 @@ allprojects {
|
|||||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||||
maven("http://maven.jzy3d.org/releases")
|
maven("http://maven.jzy3d.org/releases")
|
||||||
maven("https://kotlin.bintray.com/js-externals")
|
maven("https://kotlin.bintray.com/js-externals")
|
||||||
|
maven("https://maven.pkg.github.com/altavir/kotlin-logging/")
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "hep.dataforge"
|
group = "hep.dataforge"
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("kscience.mpp")
|
id("ru.mipt.npm.mpp")
|
||||||
id("kscience.publish")
|
id("ru.mipt.npm.publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
val ktorVersion: String by extra("1.4.0")
|
val ktorVersion: String by extra("1.4.0")
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
// js {
|
|
||||||
// browser {
|
|
||||||
// dceTask {
|
|
||||||
// keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("kscience.mpp")
|
id("ru.mipt.npm.mpp")
|
||||||
id("kscience.publish")
|
id("ru.mipt.npm.publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion: String by rootProject.extra
|
val dataforgeVersion: String by rootProject.extra
|
||||||
@ -15,7 +15,6 @@ kotlin {
|
|||||||
commonMain{
|
commonMain{
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-io:$dataforgeVersion")
|
api("hep.dataforge:dataforge-io:$dataforgeVersion")
|
||||||
//implementation("org.jetbrains.kotlinx:atomicfu-common:0.14.3")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain{
|
jvmMain{
|
||||||
|
@ -3,7 +3,6 @@ package hep.dataforge.control.api
|
|||||||
import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET
|
import hep.dataforge.control.api.Device.Companion.DEVICE_TARGET
|
||||||
import hep.dataforge.io.Envelope
|
import hep.dataforge.io.Envelope
|
||||||
import hep.dataforge.io.EnvelopeBuilder
|
import hep.dataforge.io.EnvelopeBuilder
|
||||||
import hep.dataforge.io.Responder
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
@ -15,7 +14,7 @@ import kotlinx.io.Closeable
|
|||||||
* General interface describing a managed Device
|
* General interface describing a managed Device
|
||||||
*/
|
*/
|
||||||
@Type(DEVICE_TARGET)
|
@Type(DEVICE_TARGET)
|
||||||
public interface Device : Responder, Closeable {
|
public interface Device : Closeable {
|
||||||
/**
|
/**
|
||||||
* List of supported property descriptors
|
* 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.
|
* [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() {
|
override fun close() {
|
||||||
scope.cancel("The device is closed")
|
scope.cancel("The device is closed")
|
||||||
|
@ -6,9 +6,9 @@ import hep.dataforge.meta.MetaItem
|
|||||||
* PropertyChangeListener Interface
|
* PropertyChangeListener Interface
|
||||||
* [value] is a new value that property has after a change; null is for invalid state.
|
* [value] is a new value that property has after a change; null is for invalid state.
|
||||||
*/
|
*/
|
||||||
interface DeviceListener {
|
public interface DeviceListener {
|
||||||
fun propertyChanged(propertyName: String, value: MetaItem<*>?)
|
public fun propertyChanged(propertyName: String, value: MetaItem<*>?)
|
||||||
fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {}
|
public fun actionExecuted(action: String, argument: MetaItem<*>?, result: MetaItem<*>?) {}
|
||||||
|
|
||||||
//TODO add general message listener method
|
//TODO add general message listener method
|
||||||
}
|
}
|
@ -6,17 +6,17 @@ import hep.dataforge.meta.string
|
|||||||
/**
|
/**
|
||||||
* A descriptor for property
|
* A descriptor for property
|
||||||
*/
|
*/
|
||||||
class PropertyDescriptor(name: String) : Scheme() {
|
public class PropertyDescriptor(name: String) : Scheme() {
|
||||||
val name by string(name)
|
public val name: String by string(name)
|
||||||
var info by string()
|
public var info: String? by string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A descriptor for property
|
* A descriptor for property
|
||||||
*/
|
*/
|
||||||
class ActionDescriptor(name: String) : Scheme() {
|
public class ActionDescriptor(name: String) : Scheme() {
|
||||||
val name by string(name)
|
public val name: String by string(name)
|
||||||
var info by string()
|
public var info: String? by string()
|
||||||
//var descriptor by spec(ItemDescriptor)
|
//var descriptor by spec(ItemDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,74 +1,10 @@
|
|||||||
package hep.dataforge.control.base
|
package hep.dataforge.control.base
|
||||||
|
|
||||||
import hep.dataforge.control.api.ActionDescriptor
|
import hep.dataforge.control.api.ActionDescriptor
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
interface Action {
|
public interface Action {
|
||||||
val name: String
|
public val name: String
|
||||||
val descriptor: ActionDescriptor
|
public val descriptor: ActionDescriptor
|
||||||
suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>?
|
public 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<D : DeviceBase>(
|
|
||||||
val owner: D,
|
|
||||||
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
val block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
|
||||||
) : ReadOnlyProperty<D, Action> {
|
|
||||||
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 : DeviceBase> D.request(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
block: suspend (MetaItem<*>?) -> MetaItem<*>?
|
|
||||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder, block)
|
|
||||||
|
|
||||||
fun <D : DeviceBase> D.requestValue(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
block: suspend (MetaItem<*>?) -> Any?
|
|
||||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
|
||||||
val res = block(it)
|
|
||||||
MetaItem.ValueItem(Value.of(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <D : DeviceBase> D.requestMeta(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
block: suspend MetaBuilder.(MetaItem<*>?) -> Unit
|
|
||||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
|
||||||
val res = MetaBuilder().apply { block(it) }
|
|
||||||
MetaItem.NodeItem(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <D : DeviceBase> D.action(
|
|
||||||
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
|
||||||
block: suspend (MetaItem<*>?) -> Unit
|
|
||||||
): ActionDelegate<D> = ActionDelegate(this, descriptorBuilder) {
|
|
||||||
block(it)
|
|
||||||
null
|
|
||||||
}
|
}
|
@ -5,14 +5,23 @@ import hep.dataforge.control.api.Device
|
|||||||
import hep.dataforge.control.api.DeviceListener
|
import hep.dataforge.control.api.DeviceListener
|
||||||
import hep.dataforge.control.api.PropertyDescriptor
|
import hep.dataforge.control.api.PropertyDescriptor
|
||||||
import hep.dataforge.meta.MetaItem
|
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.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baseline implementation of [Device] interface
|
* Baseline implementation of [Device] interface
|
||||||
*/
|
*/
|
||||||
abstract class DeviceBase : Device {
|
public abstract class DeviceBase : Device {
|
||||||
private val properties = HashMap<String, ReadOnlyDeviceProperty>()
|
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
|
||||||
private val actions = HashMap<String, Action>()
|
public val properties: Map<String, ReadOnlyDeviceProperty> get() = _properties
|
||||||
|
private val _actions = HashMap<String, Action>()
|
||||||
|
public val actions: Map<String, Action> get() = _actions
|
||||||
|
|
||||||
private val listeners = ArrayList<Pair<Any?, DeviceListener>>(4)
|
private val listeners = ArrayList<Pair<Any?, DeviceListener>>(4)
|
||||||
|
|
||||||
@ -24,11 +33,11 @@ abstract class DeviceBase : Device {
|
|||||||
listeners.removeAll { it.first == owner }
|
listeners.removeAll { it.first == owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyListeners(block: DeviceListener.() -> Unit) {
|
internal fun notifyListeners(block: DeviceListener.() -> Unit) {
|
||||||
listeners.forEach { it.second.block() }
|
listeners.forEach { it.second.block() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyPropertyChanged(propertyName: String) {
|
public fun notifyPropertyChanged(propertyName: String) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val value = getProperty(propertyName)
|
val value = getProperty(propertyName)
|
||||||
notifyListeners { propertyChanged(propertyName, value) }
|
notifyListeners { propertyChanged(propertyName, value) }
|
||||||
@ -36,41 +45,188 @@ abstract class DeviceBase : Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val propertyDescriptors: Collection<PropertyDescriptor>
|
override val propertyDescriptors: Collection<PropertyDescriptor>
|
||||||
get() = properties.values.map { it.descriptor }
|
get() = _properties.values.map { it.descriptor }
|
||||||
|
|
||||||
override val actionDescriptors: Collection<ActionDescriptor>
|
override val actionDescriptors: Collection<ActionDescriptor>
|
||||||
get() = actions.values.map { it.descriptor }
|
get() = _actions.values.map { it.descriptor }
|
||||||
|
|
||||||
internal fun registerProperty(name: String, builder: () -> ReadOnlyDeviceProperty): ReadOnlyDeviceProperty {
|
internal fun <P : ReadOnlyDeviceProperty> registerProperty(name: String, property: P) {
|
||||||
return properties.getOrPut(name, builder)
|
if (_properties.contains(name)) error("Property with name $name already registered")
|
||||||
|
_properties[name] = property
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun registerMutableProperty(name: String, builder: () -> DeviceProperty): DeviceProperty {
|
internal fun registerAction(name: String, action: Action) {
|
||||||
return properties.getOrPut(name, builder) as DeviceProperty
|
if (_actions.contains(name)) error("Action with name $name already registered")
|
||||||
}
|
_actions[name] = action
|
||||||
|
|
||||||
internal fun registerAction(name: String, builder: () -> Action): Action {
|
|
||||||
return actions.getOrPut(name, builder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getProperty(propertyName: String): MetaItem<*> =
|
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) {
|
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<*>) {
|
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
|
value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(command: String, argument: MetaItem<*>?): MetaItem<*>? =
|
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<MetaItem<*>?> = 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<MetaItem<*>?> = 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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<MetaItem<*>?> = 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<MetaItem<*>?> = 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<D : DeviceBase>(
|
|
||||||
val owner: D,
|
|
||||||
val default: MetaItem<*>?,
|
|
||||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
|
||||||
) : ReadOnlyProperty<D, ReadOnlyDeviceProperty> {
|
|
||||||
|
|
||||||
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 : DeviceBase> D.reading(
|
|
||||||
default: MetaItem<*>? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>
|
|
||||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
|
||||||
this,
|
|
||||||
default,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.readingValue(
|
|
||||||
default: Value? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> Any?
|
|
||||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
|
||||||
this,
|
|
||||||
default?.let { MetaItem.ValueItem(it) },
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { MetaItem.ValueItem(Value.of(getter())) }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.readingNumber(
|
|
||||||
default: Number? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> Number
|
|
||||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
|
||||||
this,
|
|
||||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val number = getter()
|
|
||||||
MetaItem.ValueItem(number.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.readingString(
|
|
||||||
default: Number? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend () -> String
|
|
||||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = ReadOnlyDevicePropertyDelegate(
|
|
||||||
this,
|
|
||||||
default?.let { MetaItem.ValueItem(it.asValue()) },
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = {
|
|
||||||
val number = getter()
|
|
||||||
MetaItem.ValueItem(number.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.readingMeta(
|
|
||||||
default: Meta? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend MetaBuilder.() -> Unit
|
|
||||||
): ReadOnlyProperty<D, ReadOnlyDeviceProperty> = 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<D : DeviceBase>(
|
|
||||||
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<D, DeviceProperty> {
|
|
||||||
|
|
||||||
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 : DeviceBase> D.writing(
|
|
||||||
default: MetaItem<*>? = null,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
|
||||||
setter: suspend (oldValue: MetaItem<*>?, newValue: MetaItem<*>) -> MetaItem<*>?
|
|
||||||
): ReadOnlyProperty<D, DeviceProperty> = DevicePropertyDelegate(
|
|
||||||
this,
|
|
||||||
default,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter,
|
|
||||||
setter
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.writingVirtual(
|
|
||||||
default: MetaItem<*>,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
|
||||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
|
||||||
default,
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { it ?: default },
|
|
||||||
setter = { _, newItem -> newItem }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.writingVirtual(
|
|
||||||
default: Value,
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
|
||||||
): ReadOnlyProperty<D, DeviceProperty> = writing(
|
|
||||||
MetaItem.ValueItem(default),
|
|
||||||
descriptorBuilder,
|
|
||||||
getter = { it ?: MetaItem.ValueItem(default) },
|
|
||||||
setter = { _, newItem -> newItem }
|
|
||||||
)
|
|
||||||
|
|
||||||
public fun <D : DeviceBase> D.writingDouble(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Double) -> Double,
|
|
||||||
setter: suspend (oldValue: Double?, newValue: Double) -> Double?
|
|
||||||
): ReadOnlyProperty<D, DeviceProperty> {
|
|
||||||
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 : DeviceBase> D.writingBoolean(
|
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
|
||||||
getter: suspend (Boolean?) -> Boolean,
|
|
||||||
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?
|
|
||||||
): ReadOnlyProperty<D, DeviceProperty> {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
@ -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 : DeviceBase> D.provideAction(): ReadOnlyProperty<D, Action> =
|
||||||
|
ReadOnlyProperty { _: D, property: KProperty<*> ->
|
||||||
|
val name = property.name
|
||||||
|
return@ReadOnlyProperty actions[name]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias ActionDelegate = ReadOnlyProperty<DeviceBase, Action>
|
||||||
|
|
||||||
|
private class ActionProvider<D : DeviceBase>(
|
||||||
|
val owner: D,
|
||||||
|
val descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
val block: suspend (MetaItem<*>?) -> MetaItem<*>?,
|
||||||
|
) : PropertyDelegateProvider<D, ActionDelegate> {
|
||||||
|
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<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder, block)
|
||||||
|
|
||||||
|
public fun <D : DeviceBase> D.requestingValue(
|
||||||
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
block: suspend (MetaItem<*>?) -> Any?,
|
||||||
|
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
|
val res = block(it)
|
||||||
|
MetaItem.ValueItem(Value.of(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <D : DeviceBase> D.requestingMeta(
|
||||||
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
block: suspend MetaBuilder.(MetaItem<*>?) -> Unit,
|
||||||
|
): PropertyDelegateProvider<D, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
|
val res = MetaBuilder().apply { block(it) }
|
||||||
|
MetaItem.NodeItem(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun DeviceBase.acting(
|
||||||
|
descriptorBuilder: ActionDescriptor.() -> Unit = {},
|
||||||
|
block: suspend (MetaItem<*>?) -> Unit,
|
||||||
|
): PropertyDelegateProvider<DeviceBase, ActionDelegate> = ActionProvider(this, descriptorBuilder) {
|
||||||
|
block(it)
|
||||||
|
null
|
||||||
|
}
|
@ -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 : DeviceBase> D.provideProperty(): ReadOnlyProperty<D, ReadOnlyDeviceProperty> =
|
||||||
|
ReadOnlyProperty { _: D, property: KProperty<*> ->
|
||||||
|
val name = property.name
|
||||||
|
return@ReadOnlyProperty properties[name]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias ReadOnlyPropertyDelegate = ReadOnlyProperty<DeviceBase, ReadOnlyDeviceProperty>
|
||||||
|
|
||||||
|
private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
|
||||||
|
val owner: D,
|
||||||
|
val default: MetaItem<*>?,
|
||||||
|
val descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
private val getter: suspend (MetaItem<*>?) -> MetaItem<*>,
|
||||||
|
) : PropertyDelegateProvider<D, ReadOnlyPropertyDelegate> {
|
||||||
|
|
||||||
|
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<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||||
|
this,
|
||||||
|
default,
|
||||||
|
descriptorBuilder,
|
||||||
|
getter
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun DeviceBase.readingValue(
|
||||||
|
default: Value? = null,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
getter: suspend () -> Any?,
|
||||||
|
): PropertyDelegateProvider<DeviceBase, ReadOnlyPropertyDelegate> = 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<DeviceBase, ReadOnlyPropertyDelegate> = 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<DeviceBase, ReadOnlyPropertyDelegate> = 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<DeviceBase, ReadOnlyPropertyDelegate> = ReadOnlyDevicePropertyProvider(
|
||||||
|
this,
|
||||||
|
default?.let { MetaItem.NodeItem(it) },
|
||||||
|
descriptorBuilder,
|
||||||
|
getter = {
|
||||||
|
MetaItem.NodeItem(MetaBuilder().apply { getter() })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun DeviceBase.provideMutableProperty(): ReadOnlyProperty<DeviceBase, DeviceProperty> =
|
||||||
|
ReadOnlyProperty { _: DeviceBase, property: KProperty<*> ->
|
||||||
|
val name = property.name
|
||||||
|
return@ReadOnlyProperty properties[name] as DeviceProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias PropertyDelegate = ReadOnlyProperty<DeviceBase, DeviceProperty>
|
||||||
|
|
||||||
|
private class DevicePropertyProvider<D : DeviceBase>(
|
||||||
|
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<D, PropertyDelegate> {
|
||||||
|
|
||||||
|
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<DeviceBase, PropertyDelegate> = DevicePropertyProvider(
|
||||||
|
this,
|
||||||
|
default,
|
||||||
|
descriptorBuilder,
|
||||||
|
getter,
|
||||||
|
setter
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun DeviceBase.writingVirtual(
|
||||||
|
default: MetaItem<*>,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||||
|
default,
|
||||||
|
descriptorBuilder,
|
||||||
|
getter = { it ?: default },
|
||||||
|
setter = { _, newItem -> newItem }
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun DeviceBase.writingVirtual(
|
||||||
|
default: Value,
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
): PropertyDelegateProvider<DeviceBase, PropertyDelegate> = writing(
|
||||||
|
MetaItem.ValueItem(default),
|
||||||
|
descriptorBuilder,
|
||||||
|
getter = { it ?: MetaItem.ValueItem(default) },
|
||||||
|
setter = { _, newItem -> newItem }
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun <D : DeviceBase> D.writingDouble(
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
getter: suspend (Double) -> Double,
|
||||||
|
setter: suspend (oldValue: Double?, newValue: Double) -> Double?,
|
||||||
|
): PropertyDelegateProvider<D, PropertyDelegate> {
|
||||||
|
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 : DeviceBase> D.writingBoolean(
|
||||||
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
|
getter: suspend (Boolean?) -> Boolean,
|
||||||
|
setter: suspend (oldValue: Boolean?, newValue: Boolean) -> Boolean?,
|
||||||
|
): PropertyDelegateProvider<D, PropertyDelegate> {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
@ -78,7 +78,7 @@ class DeviceController(
|
|||||||
} else if (target != null && target != deviceTarget) {
|
} else if (target != null && target != deviceTarget) {
|
||||||
error("Wrong target name $deviceTarget expected but $target found")
|
error("Wrong target name $deviceTarget expected but $target found")
|
||||||
} else {
|
} else {
|
||||||
val response = device.respond(request).apply {
|
val response = device.respondWithData(request).apply {
|
||||||
meta {
|
meta {
|
||||||
"target" put request.meta["source"].string
|
"target" put request.meta["source"].string
|
||||||
"source" put deviceTarget
|
"source" put deviceTarget
|
||||||
|
@ -8,6 +8,7 @@ import hep.dataforge.values.Null
|
|||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> =
|
operator fun ReadOnlyDeviceProperty.getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*> =
|
||||||
value ?: MetaItem.ValueItem(Null)
|
value ?: MetaItem.ValueItem(Null)
|
||||||
@ -17,10 +18,8 @@ operator fun DeviceProperty.setValue(thisRef: Any?, property: KProperty<*>, valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter<T>): ReadOnlyProperty<Any?, T> {
|
fun <T : Any> ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter<T>): ReadOnlyProperty<Any?, T> {
|
||||||
return object : ReadOnlyProperty<Any?, T> {
|
return ReadOnlyProperty { thisRef, property ->
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
getValue(thisRef, property).let { metaConverter.itemToObject(it) }
|
||||||
return this@convert.getValue(thisRef, property).let { metaConverter.itemToObject(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,3 +43,6 @@ fun DeviceProperty.int() = convert(MetaConverter.int)
|
|||||||
|
|
||||||
fun ReadOnlyDeviceProperty.string() = convert(MetaConverter.string)
|
fun ReadOnlyDeviceProperty.string() = convert(MetaConverter.string)
|
||||||
fun DeviceProperty.string() = convert(MetaConverter.string)
|
fun DeviceProperty.string() = convert(MetaConverter.string)
|
||||||
|
|
||||||
|
fun ReadOnlyDeviceProperty.duration(): ReadOnlyProperty<Any?, Duration> = TODO()
|
||||||
|
fun DeviceProperty.duration(): ReadWriteProperty<Any?, Duration> = TODO()
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("kscience.jvm")
|
id("ru.mipt.npm.jvm")
|
||||||
id("kscience.publish")
|
id("ru.mipt.npm.publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("kscience.jvm")
|
id("ru.mipt.npm.jvm")
|
||||||
id("kscience.publish")
|
id("ru.mipt.npm.publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
@ -8,7 +8,7 @@ kscience {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion: String by rootProject.extra
|
val dataforgeVersion: String by rootProject.extra
|
||||||
val ktorVersion: String by extra("1.3.2")
|
val ktorVersion: String by extra("1.4.0")
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
implementation(project(":dataforge-device-core"))
|
implementation(project(":dataforge-device-core"))
|
||||||
|
@ -6,6 +6,7 @@ plugins {
|
|||||||
|
|
||||||
|
|
||||||
repositories{
|
repositories{
|
||||||
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven("https://kotlin.bintray.com/kotlinx")
|
maven("https://kotlin.bintray.com/kotlinx")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||||
@ -21,7 +22,7 @@ dependencies{
|
|||||||
implementation(project(":dataforge-device-client"))
|
implementation(project(":dataforge-device-client"))
|
||||||
implementation("no.tornado:tornadofx:1.7.20")
|
implementation("no.tornado:tornadofx:1.7.20")
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation("kscience.plotlykt:plotlykt-server:0.2.0")
|
implementation("kscience.plotlykt:plotlykt-server:0.3.0-dev-2")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
|
@ -50,7 +50,7 @@ class DemoDevice(parentScope: CoroutineScope) : DeviceBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val resetScale: Action by action {
|
val resetScale: Action by acting {
|
||||||
timeScaleValue = 5000.0
|
timeScaleValue = 5000.0
|
||||||
sinScaleValue = 1.0
|
sinScaleValue = 1.0
|
||||||
cosScaleValue = 1.0
|
cosScaleValue = 1.0
|
||||||
|
BIN
docs/schemes/direct-vs-loop.vsdx
Normal file
BIN
docs/schemes/direct-vs-loop.vsdx
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("kscience.jvm")
|
id("ru.mipt.npm.jvm")
|
||||||
id("kscience.publish")
|
id("ru.mipt.npm.publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO to be moved to a separate project
|
//TODO to be moved to a separate project
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package ru.mipt.npm.devices.pimotionmaster
|
package ru.mipt.npm.devices.pimotionmaster
|
||||||
|
|
||||||
|
import hep.dataforge.control.api.DeviceHub
|
||||||
import hep.dataforge.control.base.*
|
import hep.dataforge.control.base.*
|
||||||
|
import hep.dataforge.control.controllers.duration
|
||||||
import hep.dataforge.control.ports.Port
|
import hep.dataforge.control.ports.Port
|
||||||
import hep.dataforge.control.ports.PortProxy
|
import hep.dataforge.control.ports.PortProxy
|
||||||
import hep.dataforge.control.ports.send
|
import hep.dataforge.control.ports.send
|
||||||
import hep.dataforge.control.ports.withDelimiter
|
import hep.dataforge.control.ports.withDelimiter
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.values.Null
|
import hep.dataforge.values.Null
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -14,11 +17,15 @@ import kotlinx.coroutines.flow.takeWhile
|
|||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
||||||
public class PiMotionMasterDevice(
|
public class PiMotionMasterDevice(
|
||||||
parentScope: CoroutineScope,
|
parentScope: CoroutineScope,
|
||||||
|
axes: List<String>,
|
||||||
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
private val portFactory: suspend (MetaItem<*>?) -> Port,
|
||||||
) : DeviceBase() {
|
) : DeviceBase(), DeviceHub {
|
||||||
|
|
||||||
override val scope: CoroutineScope = CoroutineScope(
|
override val scope: CoroutineScope = CoroutineScope(
|
||||||
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
parentScope.coroutineContext + Job(parentScope.coroutineContext[Job])
|
||||||
@ -28,11 +35,17 @@ public class PiMotionMasterDevice(
|
|||||||
info = "The port for TCP connector"
|
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 connector = PortProxy { portFactory(port.value) }
|
||||||
|
|
||||||
private val mutex = Mutex()
|
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()) {
|
val joinedArguments = if (arguments.isEmpty()) {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
@ -46,9 +59,11 @@ public class PiMotionMasterDevice(
|
|||||||
* Send a synchronous request and receive a list of lines as a response
|
* Send a synchronous request and receive a list of lines as a response
|
||||||
*/
|
*/
|
||||||
private suspend fun request(command: String, vararg arguments: String): List<String> = mutex.withLock {
|
private suspend fun request(command: String, vararg arguments: String): List<String> = mutex.withLock {
|
||||||
sendCommand(command, *arguments)
|
withTimeout(timeoutValue) {
|
||||||
|
sendCommandInternal(command, *arguments)
|
||||||
val phrases = connector.receiving().withDelimiter("\n")
|
val phrases = connector.receiving().withDelimiter("\n")
|
||||||
return@withLock phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first()
|
phrases.takeWhile { it.endsWith(" \n") }.toList() + phrases.first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
|
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
|
||||||
@ -63,11 +78,13 @@ public class PiMotionMasterDevice(
|
|||||||
*/
|
*/
|
||||||
private suspend fun send(command: String, vararg arguments: String) {
|
private suspend fun send(command: String, vararg arguments: String) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
sendCommand(command, *arguments)
|
withTimeout(timeoutValue) {
|
||||||
|
sendCommandInternal(command, *arguments)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public val initialize: Action by action {
|
public val initialize: Action by acting {
|
||||||
send("INI")
|
send("INI")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,32 +96,37 @@ public class PiMotionMasterDevice(
|
|||||||
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
|
override val scope: CoroutineScope get() = this@PiMotionMasterDevice.scope
|
||||||
public val enabled: DeviceProperty by writingBoolean<Axis>(
|
public val enabled: DeviceProperty by writingBoolean<Axis>(
|
||||||
getter = {
|
getter = {
|
||||||
val result = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull()
|
val eax = requestAndParse("EAX?", axisId)[axisId]?.toIntOrNull()
|
||||||
?: error("Malformed response. Should include integer value for $axisId")
|
?: error("Malformed EAX response. Should include integer value for $axisId")
|
||||||
result != 0
|
eax != 0
|
||||||
},
|
},
|
||||||
setter = { oldValue, newValue ->
|
setter = { _, newValue ->
|
||||||
val value = if (newValue) {
|
val value = if (newValue) {
|
||||||
"1"
|
"1"
|
||||||
} else {
|
} else {
|
||||||
"0"
|
"0"
|
||||||
}
|
}
|
||||||
send("EAX", axisId, value)
|
send("EAX", axisId, value)
|
||||||
oldValue
|
newValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
public val halt: Action by action {
|
public val halt: Action by acting {
|
||||||
send("HLT", axisId)
|
send("HLT", axisId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public val targetPosition: DeviceProperty by writingDouble<Axis>(
|
||||||
|
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 {
|
override val devices: Map<NameToken, Axis> = axes.associate { NameToken(it) to Axis(it) }
|
||||||
//list everything here to ensure it is initialized
|
|
||||||
initialize
|
|
||||||
firmwareVersion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
val kotlinVersion = "1.4.0"
|
val kotlinVersion = "1.4.0"
|
||||||
val toolsVersion = "0.6.0-dev-1"
|
val toolsVersion = "0.6.0-dev-4"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@ -9,25 +9,17 @@ pluginManagement {
|
|||||||
maven("https://kotlin.bintray.com/kotlinx")
|
maven("https://kotlin.bintray.com/kotlinx")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||||
maven("https://dl.bintray.com/mipt-npm/dataforge")
|
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")
|
maven("https://dl.bintray.com/mipt-npm/dev")
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
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
|
kotlin("jvm") version kotlinVersion
|
||||||
id("scientifik.mpp") version toolsVersion
|
kotlin("js") version kotlinVersion
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user