Refactoring properties getters
This commit is contained in:
parent
fe3958fd08
commit
596e3a0cfc
@ -29,21 +29,25 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||
public val actionDescriptors: Collection<ActionDescriptor>
|
||||
|
||||
/**
|
||||
* Get the value of the property or throw error if property in not defined.
|
||||
* Suspend if property value is not available
|
||||
* Read physical state of property and update/push notifications if needed.
|
||||
*/
|
||||
public suspend fun getProperty(propertyName: String): MetaItem
|
||||
public suspend fun readItem(propertyName: String): MetaItem
|
||||
|
||||
/**
|
||||
* Invalidate property and force recalculate
|
||||
* Get the logical state of property or return null if it is invalid
|
||||
*/
|
||||
public suspend fun invalidateProperty(propertyName: String)
|
||||
public fun getItem(propertyName: String): MetaItem?
|
||||
|
||||
/**
|
||||
* Invalidate property (set logical state to invalid)
|
||||
*/
|
||||
public suspend fun invalidate(propertyName: String)
|
||||
|
||||
/**
|
||||
* Set property [value] for a property with name [propertyName].
|
||||
* In rare cases could suspend if the [Device] supports command queue and it is full at the moment.
|
||||
*/
|
||||
public suspend fun setProperty(propertyName: String, value: MetaItem)
|
||||
public suspend fun writeItem(propertyName: String, value: MetaItem)
|
||||
|
||||
/**
|
||||
* The [SharedFlow] of property changes
|
||||
@ -65,9 +69,19 @@ public interface Device : Closeable, ContextAware, CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun Device.getState(): Meta = Meta{
|
||||
for(descriptor in propertyDescriptors) {
|
||||
descriptor.name put getProperty(descriptor.name)
|
||||
|
||||
/**
|
||||
* Get the logical state of property or suspend to read the physical value.
|
||||
*/
|
||||
public suspend fun Device.getOrReadItem(propertyName: String): MetaItem =
|
||||
getItem(propertyName) ?: readItem(propertyName)
|
||||
|
||||
/**
|
||||
* Get a snapshot of logical state of the device
|
||||
*/
|
||||
public fun Device.getProperties(): Meta = Meta {
|
||||
for (descriptor in propertyDescriptors) {
|
||||
descriptor.name put getItem(descriptor.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,11 +58,11 @@ public fun DeviceHub.getOrNull(nameString: String): Device? = getOrNull(nameStri
|
||||
public operator fun DeviceHub.get(nameString: String): Device =
|
||||
getOrNull(nameString) ?: error("Device with name $nameString not found in $this")
|
||||
|
||||
public suspend fun DeviceHub.getProperty(deviceName: Name, propertyName: String): MetaItem =
|
||||
this[deviceName].getProperty(propertyName)
|
||||
public suspend fun DeviceHub.readItem(deviceName: Name, propertyName: String): MetaItem =
|
||||
this[deviceName].readItem(propertyName)
|
||||
|
||||
public suspend fun DeviceHub.setProperty(deviceName: Name, propertyName: String, value: MetaItem) {
|
||||
this[deviceName].setProperty(propertyName, value)
|
||||
public suspend fun DeviceHub.writeItem(deviceName: Name, propertyName: String, value: MetaItem) {
|
||||
this[deviceName].writeItem(propertyName, value)
|
||||
}
|
||||
|
||||
public suspend fun DeviceHub.execute(deviceName: Name, command: String, argument: MetaItem?): MetaItem? =
|
||||
|
@ -152,14 +152,17 @@ public abstract class DeviceBase(final override val context: Context) : Device {
|
||||
_actions[name] = action
|
||||
}
|
||||
|
||||
override suspend fun getProperty(propertyName: String): MetaItem =
|
||||
override suspend fun readItem(propertyName: String): MetaItem =
|
||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).read()
|
||||
|
||||
override suspend fun invalidateProperty(propertyName: String) {
|
||||
override fun getItem(propertyName: String): MetaItem?=
|
||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).value
|
||||
|
||||
override suspend fun invalidate(propertyName: String) {
|
||||
(_properties[propertyName] ?: error("Property with name $propertyName not defined")).invalidate()
|
||||
}
|
||||
|
||||
override suspend fun setProperty(propertyName: String, value: MetaItem) {
|
||||
override suspend fun writeItem(propertyName: String, value: MetaItem) {
|
||||
(_properties[propertyName] as? DeviceProperty ?: error("Property with name $propertyName not defined")).write(
|
||||
value
|
||||
)
|
||||
|
@ -67,7 +67,7 @@ public class DeviceController(
|
||||
is PropertyGetMessage -> {
|
||||
PropertyChangedMessage(
|
||||
property = request.property,
|
||||
value = device.getProperty(request.property),
|
||||
value = device.getOrReadItem(request.property),
|
||||
sourceDevice = deviceTarget,
|
||||
targetDevice = request.sourceDevice
|
||||
)
|
||||
@ -75,13 +75,13 @@ public class DeviceController(
|
||||
|
||||
is PropertySetMessage -> {
|
||||
if (request.value == null) {
|
||||
device.invalidateProperty(request.property)
|
||||
device.invalidate(request.property)
|
||||
} else {
|
||||
device.setProperty(request.property, request.value)
|
||||
device.writeItem(request.property, request.value)
|
||||
}
|
||||
PropertyChangedMessage(
|
||||
property = request.property,
|
||||
value = device.getProperty(request.property),
|
||||
value = device.getOrReadItem(request.property),
|
||||
sourceDevice = deviceTarget,
|
||||
targetDevice = request.sourceDevice
|
||||
)
|
||||
|
@ -1,8 +1,10 @@
|
||||
package ru.mipt.npm.controls.properties
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import ru.mipt.npm.controls.api.ActionDescriptor
|
||||
@ -21,6 +23,7 @@ import kotlin.reflect.KProperty
|
||||
/**
|
||||
* @param D recursive self-type for properties and actions
|
||||
*/
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
public val spec: DeviceSpec<D>,
|
||||
context: Context = Global,
|
||||
@ -55,11 +58,9 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
internal val self: D
|
||||
get() = this as D
|
||||
|
||||
internal fun getLogicalState(propertyName: String): MetaItem? = logicalState[propertyName]
|
||||
|
||||
private val stateLock = Mutex()
|
||||
|
||||
internal suspend fun updateLogical(propertyName: String, value: MetaItem?) {
|
||||
private suspend fun updateLogical(propertyName: String, value: MetaItem?) {
|
||||
if (value != logicalState[propertyName]) {
|
||||
stateLock.withLock {
|
||||
logicalState[propertyName] = value
|
||||
@ -74,27 +75,26 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
* Force read physical value and push an update if it is changed. It does not matter if logical state is present.
|
||||
* The logical state is updated after read
|
||||
*/
|
||||
public suspend fun readProperty(propertyName: String): MetaItem {
|
||||
override suspend fun readItem(propertyName: String): MetaItem {
|
||||
val newValue = properties[propertyName]?.readItem(self)
|
||||
?: error("A property with name $propertyName is not registered in $this")
|
||||
updateLogical(propertyName, newValue)
|
||||
return newValue
|
||||
}
|
||||
|
||||
override suspend fun getProperty(propertyName: String): MetaItem =
|
||||
logicalState[propertyName] ?: readProperty(propertyName)
|
||||
override fun getItem(propertyName: String): MetaItem? = logicalState[propertyName]
|
||||
|
||||
override suspend fun invalidateProperty(propertyName: String) {
|
||||
override suspend fun invalidate(propertyName: String) {
|
||||
stateLock.withLock {
|
||||
logicalState.remove(propertyName)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setProperty(propertyName: String, value: MetaItem): Unit {
|
||||
override suspend fun writeItem(propertyName: String, value: MetaItem): Unit {
|
||||
//If there is a physical property with given name, invalidate logical property and write physical one
|
||||
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any>)?.let {
|
||||
it.writeItem(self, value)
|
||||
invalidateProperty(propertyName)
|
||||
invalidate(propertyName)
|
||||
} ?: run {
|
||||
updateLogical(propertyName, value)
|
||||
}
|
||||
@ -113,36 +113,44 @@ public open class DeviceBySpec<D : DeviceBySpec<D>>(
|
||||
): ReadWriteProperty<D, T> = observable(initialValue) { property: KProperty<*>, oldValue: T, newValue: T ->
|
||||
if (oldValue != newValue) {
|
||||
launch {
|
||||
invalidateProperty(property.name)
|
||||
invalidate(property.name)
|
||||
_propertyFlow.emit(property.name to converter.objectToMetaItem(newValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T = read(self)
|
||||
/**
|
||||
* Read typed value and update/push event if needed
|
||||
*/
|
||||
public suspend fun <T : Any> DevicePropertySpec<D, T>.read(): T {
|
||||
val res = read(self)
|
||||
updateLogical(name, converter.objectToMetaItem(res))
|
||||
return res
|
||||
}
|
||||
|
||||
public fun <T : Any> DevicePropertySpec<D, T>.get(): T? = getItem(name)?.let(converter::itemToObject)
|
||||
|
||||
/**
|
||||
* Write typed property state and invalidate logical state
|
||||
*/
|
||||
public suspend fun <T : Any> WritableDevicePropertySpec<D, T>.write(value: T) {
|
||||
write(self, value)
|
||||
invalidate(name)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
with(spec){ self.onShutdown() }
|
||||
with(spec) { self.onShutdown() }
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun <D : DeviceBySpec<D>, T : Any> D.getSuspend(
|
||||
public suspend fun <D : DeviceBySpec<D>, T : Any> D.read(
|
||||
propertySpec: DevicePropertySpec<D, T>
|
||||
): T = propertySpec.read(this@getSuspend).also {
|
||||
updateLogical(propertySpec.name, propertySpec.converter.objectToMetaItem(it))
|
||||
}
|
||||
): T = propertySpec.read()
|
||||
|
||||
|
||||
public fun <D : DeviceBySpec<D>, T : Any> D.getAsync(
|
||||
propertySpec: DevicePropertySpec<D, T>
|
||||
): Deferred<T> = async {
|
||||
getSuspend(propertySpec)
|
||||
}
|
||||
|
||||
public operator fun <D : DeviceBySpec<D>, T : Any> D.set(propertySpec: WritableDevicePropertySpec<D, T>, value: T) {
|
||||
launch {
|
||||
propertySpec.write(this@set, value)
|
||||
invalidateProperty(propertySpec.name)
|
||||
}
|
||||
public fun <D : DeviceBySpec<D>, T : Any> D.write(
|
||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||
value: T
|
||||
): Job = launch {
|
||||
propertySpec.write(value)
|
||||
}
|
@ -8,6 +8,13 @@ import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import space.kscience.dataforge.meta.transformations.nullableItemToObject
|
||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
||||
|
||||
|
||||
/**
|
||||
* This API is internal and should not be used in user code
|
||||
*/
|
||||
@RequiresOptIn
|
||||
public annotation class InternalDeviceAPI
|
||||
|
||||
//TODO relax T restriction after DF 0.4.4
|
||||
public interface DevicePropertySpec<in D : Device, T : Any> {
|
||||
/**
|
||||
@ -28,9 +35,11 @@ public interface DevicePropertySpec<in D : Device, T : Any> {
|
||||
/**
|
||||
* Read physical value from the given [device]
|
||||
*/
|
||||
@InternalDeviceAPI
|
||||
public suspend fun read(device: D): T
|
||||
}
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public suspend fun <D : Device, T : Any> DevicePropertySpec<D, T>.readItem(device: D): MetaItem =
|
||||
converter.objectToMetaItem(read(device))
|
||||
|
||||
@ -39,9 +48,11 @@ public interface WritableDevicePropertySpec<in D : Device, T : Any> : DeviceProp
|
||||
/**
|
||||
* Write physical value to a device
|
||||
*/
|
||||
@InternalDeviceAPI
|
||||
public suspend fun write(device: D, value: T)
|
||||
}
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public suspend fun <D : Device, T : Any> WritableDevicePropertySpec<D, T>.writeItem(device: D, item: MetaItem) {
|
||||
write(device, converter.itemToObject(item))
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceSpec<D : DeviceBySpec<D>>(
|
||||
private val buildDevice: () -> D
|
||||
) : Factory<D> {
|
||||
|
@ -0,0 +1,32 @@
|
||||
package ru.mipt.npm.controls.properties
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Perform a recurring asynchronous read action and return a flow of results.
|
||||
* The flow is lazy so action is not performed unless flow is consumed.
|
||||
* The flow uses called context. In order to call it on device context, use `flowOn(coroutineContext)`.
|
||||
*
|
||||
* The flow is canceled when the device scope is canceled
|
||||
*/
|
||||
public fun <D : DeviceBySpec<D>, R> D.readRecurring(interval: Duration, reader: suspend D.() -> R): Flow<R> = flow {
|
||||
while (isActive) {
|
||||
kotlinx.coroutines.delay(interval)
|
||||
emit(reader())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a recurring task on a device. The task could
|
||||
*/
|
||||
public fun <D : DeviceBySpec<D>> D.doRecurring(interval: Duration, task: suspend D.() -> Unit): Job = launch {
|
||||
while (isActive) {
|
||||
kotlinx.coroutines.delay(interval)
|
||||
task()
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
package ru.mipt.npm.controls.properties
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.mipt.npm.controls.api.PropertyDescriptor
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.reflect.KFunction
|
||||
|
||||
/**
|
||||
* Blocking property get call
|
||||
*/
|
||||
public operator fun <D : DeviceBySpec<D>, T : Any> D.get(
|
||||
propertySpec: DevicePropertySpec<D, T>
|
||||
): T = runBlocking { getAsync(propertySpec).await() }
|
||||
): T = runBlocking { read(propertySpec) }
|
@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import ru.mipt.npm.controls.api.get
|
||||
import ru.mipt.npm.controls.api.getOrReadItem
|
||||
import ru.mipt.npm.controls.controllers.DeviceManager
|
||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||
import ru.mipt.npm.magix.api.MagixMessage
|
||||
@ -83,7 +84,7 @@ public fun DeviceManager.launchTangoMagix(
|
||||
val device = get(request.payload.device)
|
||||
when (request.payload.action) {
|
||||
TangoAction.read -> {
|
||||
val value = device.getProperty(request.payload.name)
|
||||
val value = device.getOrReadItem(request.payload.name)
|
||||
respond(request) { requestPayload ->
|
||||
requestPayload.copy(
|
||||
value = value,
|
||||
@ -93,10 +94,10 @@ public fun DeviceManager.launchTangoMagix(
|
||||
}
|
||||
TangoAction.write -> {
|
||||
request.payload.value?.let { value ->
|
||||
device.setProperty(request.payload.name, value)
|
||||
device.writeItem(request.payload.name, value)
|
||||
}
|
||||
//wait for value to be written and return final state
|
||||
val value = device.getProperty(request.payload.name)
|
||||
val value = device.getOrReadItem(request.payload.name)
|
||||
respond(request) { requestPayload ->
|
||||
requestPayload.copy(
|
||||
value = value,
|
||||
|
@ -1,17 +1,16 @@
|
||||
package ru.mipt.npm.controls.demo
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.mipt.npm.controls.properties.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
|
||||
class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
||||
var timeScale by state(5000.0)
|
||||
var sinScale by state( 1.0)
|
||||
var sinScale by state(1.0)
|
||||
var cosScale by state(1.0)
|
||||
|
||||
companion object : DeviceSpec<DemoDevice>(::DemoDevice) {
|
||||
@ -34,8 +33,8 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
||||
Meta {
|
||||
val time = Instant.now()
|
||||
"time" put time.toEpochMilli()
|
||||
"x" put getSuspend(sin)
|
||||
"y" put getSuspend(cos)
|
||||
"x" put read(sin)
|
||||
"y" put read(cos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,13 +45,11 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
||||
null
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
override fun DemoDevice.onStartup() {
|
||||
launch {
|
||||
while(isActive){
|
||||
delay(50)
|
||||
sin.read()
|
||||
cos.read()
|
||||
}
|
||||
doRecurring(Duration.milliseconds(50)){
|
||||
sin.read()
|
||||
cos.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user