Unisolate properties

This commit is contained in:
Alexander Nozik 2020-09-08 10:13:14 +03:00
parent 10bb85ac83
commit 4b5bc40a4f
21 changed files with 524 additions and 493 deletions

View File

@ -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"

View File

@ -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 {

View File

@ -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{

View File

@ -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")

View File

@ -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
}

View File

@ -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)
}

View File

@ -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<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
public interface Action {
public val name: String
public val descriptor: ActionDescriptor
public suspend operator fun invoke(arg: MetaItem<*>? = null): MetaItem<*>?
}

View File

@ -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<String, ReadOnlyDeviceProperty>()
private val actions = HashMap<String, Action>()
public abstract class DeviceBase : Device {
private val _properties = HashMap<String, ReadOnlyDeviceProperty>()
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)
@ -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<PropertyDescriptor>
get() = properties.values.map { it.descriptor }
get() = _properties.values.map { it.descriptor }
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 {
return properties.getOrPut(name, builder)
internal fun <P : ReadOnlyDeviceProperty> 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<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 {
}
}

View File

@ -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
)
}

View File

@ -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
}

View File

@ -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
)
}

View File

@ -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

View File

@ -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 <T : Any> ReadOnlyDeviceProperty.convert(metaConverter: MetaConverter<T>): ReadOnlyProperty<Any?, T> {
return object : ReadOnlyProperty<Any?, T> {
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) }
}
}
@ -44,3 +43,6 @@ fun DeviceProperty.int() = convert(MetaConverter.int)
fun ReadOnlyDeviceProperty.string() = convert(MetaConverter.string)
fun DeviceProperty.string() = convert(MetaConverter.string)
fun ReadOnlyDeviceProperty.duration(): ReadOnlyProperty<Any?, Duration> = TODO()
fun DeviceProperty.duration(): ReadWriteProperty<Any?, Duration> = TODO()

View File

@ -1,6 +1,6 @@
plugins {
id("kscience.jvm")
id("kscience.publish")
id("ru.mipt.npm.jvm")
id("ru.mipt.npm.publish")
}
dependencies{

View File

@ -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"))

View File

@ -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<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {

View File

@ -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

Binary file not shown.

View File

@ -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

View File

@ -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<String>,
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<String> = mutex.withLock {
sendCommand(command, *arguments)
withTimeout(timeoutValue) {
sendCommandInternal(command, *arguments)
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 {
@ -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<Axis>(
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<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 {
//list everything here to ensure it is initialized
initialize
firmwareVersion
}
override val devices: Map<NameToken, Axis> = axes.associate { NameToken(it) to Axis(it) }
}

View File

@ -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
}
}