Message class hierarchy.

This commit is contained in:
Alexander Nozik 2020-12-02 12:35:16 +03:00
parent 92ab801967
commit 17beb29217
18 changed files with 239 additions and 167 deletions

View File

@ -4,8 +4,8 @@ plugins {
kotlin("js") apply false kotlin("js") apply false
} }
val dataforgeVersion: String by extra("0.2.0-dev-4") val dataforgeVersion: String by extra("0.2.0")
val ktorVersion: String by extra("1.4.1") val ktorVersion: String by extra("1.4.3")
val rsocketVersion by extra("0.11.1") val rsocketVersion by extra("0.11.1")
allprojects { allprojects {

View File

@ -121,7 +121,7 @@ public abstract class DeviceBase(override val context: Context) : Device {
/** /**
* Create a bound read-only property with given [getter] * Create a bound read-only property with given [getter]
*/ */
public fun newReadOnlyProperty( public fun createReadOnlyProperty(
name: String, name: String,
default: MetaItem<*>?, default: MetaItem<*>?,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
@ -178,7 +178,7 @@ public abstract class DeviceBase(override val context: Context) : Device {
/** /**
* Create a bound mutable property with given [getter] and [setter] * Create a bound mutable property with given [getter] and [setter]
*/ */
public fun newMutableProperty( internal fun createMutableProperty(
name: String, name: String,
default: MetaItem<*>?, default: MetaItem<*>?,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},
@ -217,7 +217,7 @@ public abstract class DeviceBase(override val context: Context) : Device {
/** /**
* Create a new bound action * Create a new bound action
*/ */
public fun newAction( internal fun createAction(
name: String, name: String,
descriptorBuilder: ActionDescriptor.() -> Unit = {}, descriptorBuilder: ActionDescriptor.() -> Unit = {},
block: suspend (MetaItem<*>?) -> MetaItem<*>?, block: suspend (MetaItem<*>?) -> MetaItem<*>?,

View File

@ -24,7 +24,7 @@ private class ActionProvider<D : DeviceBase>(
) : PropertyDelegateProvider<D, ActionDelegate> { ) : PropertyDelegateProvider<D, ActionDelegate> {
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ActionDelegate {
val name = property.name val name = property.name
owner.newAction(name, descriptorBuilder, block) owner.createAction(name, descriptorBuilder, block)
return owner.provideAction() return owner.provideAction()
} }
} }

View File

@ -36,7 +36,7 @@ private class ReadOnlyDevicePropertyProvider<D : DeviceBase>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): ReadOnlyPropertyDelegate {
val name = property.name val name = property.name
owner.newReadOnlyProperty(name, default, descriptorBuilder, getter) owner.createReadOnlyProperty(name, default, descriptorBuilder, getter)
return owner.provideProperty(name) return owner.provideProperty(name)
} }
} }
@ -51,7 +51,7 @@ private class TypedReadOnlyDevicePropertyProvider<D : DeviceBase, T : Any>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedReadOnlyPropertyDelegate<T> {
val name = property.name val name = property.name
owner.newReadOnlyProperty(name, default, descriptorBuilder, getter) owner.createReadOnlyProperty(name, default, descriptorBuilder, getter)
return owner.provideProperty(name, converter) return owner.provideProperty(name, converter)
} }
} }
@ -178,7 +178,7 @@ private class DevicePropertyProvider<D : DeviceBase>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): PropertyDelegate {
val name = property.name val name = property.name
owner.newMutableProperty(name, default, descriptorBuilder, getter, setter) owner.createMutableProperty(name, default, descriptorBuilder, getter, setter)
return owner.provideMutableProperty(name) return owner.provideMutableProperty(name)
} }
} }
@ -194,7 +194,7 @@ private class TypedDevicePropertyProvider<D : DeviceBase, T : Any>(
override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> { override operator fun provideDelegate(thisRef: D, property: KProperty<*>): TypedPropertyDelegate<T> {
val name = property.name val name = property.name
owner.newMutableProperty(name, default, descriptorBuilder, getter, setter) owner.createMutableProperty(name, default, descriptorBuilder, getter, setter)
return owner.provideMutableProperty(name, converter) return owner.provideMutableProperty(name, converter)
} }
} }

View File

@ -1,11 +1,7 @@
package hep.dataforge.control.controllers package hep.dataforge.control.controllers
import hep.dataforge.control.api.* import hep.dataforge.control.api.*
import hep.dataforge.control.messages.DeviceMessage import hep.dataforge.control.messages.*
import hep.dataforge.control.messages.DeviceMessage.Companion.PROPERTY_CHANGED_ACTION
import hep.dataforge.control.messages.respondsTo
import hep.dataforge.control.messages.toEnvelope
import hep.dataforge.control.messages.toMeta
import hep.dataforge.io.Consumer import hep.dataforge.io.Consumer
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
@ -23,7 +19,7 @@ import kotlinx.io.Binary
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
public class DeviceController( public class DeviceController(
public val device: Device, public val device: Device,
public val deviceTarget: String, public val deviceName: String,
public val scope: CoroutineScope = device.scope, public val scope: CoroutineScope = device.scope,
) : Responder, Consumer, DeviceListener { ) : Responder, Consumer, DeviceListener {
@ -34,16 +30,15 @@ public class DeviceController(
private val outputChannel = Channel<Envelope>(Channel.CONFLATED) private val outputChannel = Channel<Envelope>(Channel.CONFLATED)
public suspend fun respondMessage(message: DeviceMessage): DeviceMessage = public suspend fun respondMessage(message: DeviceMessage): DeviceMessage =
respondMessage(device, deviceTarget, message) respondMessage(device, deviceName, message)
override suspend fun respond(request: Envelope): Envelope = respond(device, deviceTarget, request) override suspend fun respond(request: Envelope): Envelope = respond(device, deviceName, request)
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) { override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
if (value == null) return if (value == null) return
scope.launch { scope.launch {
val change = DeviceMessage( val change = PropertyChangedMessage(
sourceName = deviceTarget, sourceDevice = deviceName,
action = PROPERTY_CHANGED_ACTION,
key = propertyName, key = propertyName,
value = value, value = value,
) )
@ -89,7 +84,8 @@ public class DeviceController(
} else error("Device does not support binary response") } else error("Device does not support binary response")
} }
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.error(ex).toEnvelope() val requestSourceName = request.meta[DeviceMessage.SOURCE_KEY].string
DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = requestSourceName).toEnvelope()
} }
} }
@ -98,58 +94,68 @@ public class DeviceController(
deviceTarget: String, deviceTarget: String,
request: DeviceMessage, request: DeviceMessage,
): DeviceMessage = try { ): DeviceMessage = try {
val requestKey = request.key when (request) {
val requestValue = request.value is PropertyGetMessage -> {
var key: String? = null PropertyChangedMessage(
var value: MetaItem<*>? = null key = request.property,
when (val action = request.action) { value = device.getProperty(request.property),
GET_PROPERTY_ACTION -> { sourceDevice = deviceTarget,
key = requestKey targetDevice = request.sourceDevice
value = device.getProperty(requestKey ?: error("Key field is not defined in request")) )
} }
SET_PROPERTY_ACTION -> { is PropertySetMessage -> {
require(requestKey != null) { "Key field is not defined in request" } if (request.value == null) {
if (requestValue == null) { device.invalidateProperty(request.property)
device.invalidateProperty(requestKey)
} else { } else {
device.setProperty(requestKey, requestValue) device.setProperty(request.property, request.value)
} }
key = requestKey PropertyChangedMessage(
value = device.getProperty(requestKey) key = request.property,
value = device.getProperty(request.property),
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
} }
EXECUTE_ACTION -> { is ActionExecuteMessage -> {
require(requestKey != null) { "Key field is not defined in request" } ActionResultMessage(
key = requestKey action = request.action,
value = device.execute(requestKey, requestValue) result = device.execute(request.action, request.argument),
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice
)
} }
PROPERTY_LIST_ACTION -> { is GetDescriptionMessage -> {
value = Meta { val descriptionMeta = Meta {
"properties" put {
device.propertyDescriptors.map { descriptor -> device.propertyDescriptors.map { descriptor ->
descriptor.name put descriptor.config descriptor.name put descriptor.config
} }
}.asMetaItem()
} }
ACTION_LIST_ACTION -> { "actions" put {
value = Meta {
device.actionDescriptors.map { descriptor -> device.actionDescriptors.map { descriptor ->
descriptor.name put descriptor.config descriptor.name put descriptor.config
} }
}.asMetaItem()
}
else -> {
error("Unrecognized action $action")
} }
} }
DeviceMessage(
targetName = request.sourceName, DescriptionMessage(
sourceName = deviceTarget, description = descriptionMeta,
action = "response.${request.action}", sourceDevice = deviceTarget,
key = key, targetDevice = request.sourceDevice
value = value
) )
}
is DescriptionMessage, is PropertyChangedMessage, is ActionResultMessage, is BinaryNotificationMessage, is DeviceErrorMessage, is EmptyDeviceMessage -> {
//Those messages are ignored
EmptyDeviceMessage(
sourceDevice = deviceTarget,
targetDevice = request.sourceDevice,
comment = "The message is ignored"
)
}
}
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.error(ex, request.action).respondsTo(request) DeviceMessage.error(ex, sourceDevice = deviceTarget, targetDevice = request.sourceDevice)
} }
} }
} }
@ -157,10 +163,10 @@ public class DeviceController(
public suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage { public suspend fun DeviceHub.respondMessage(request: DeviceMessage): DeviceMessage {
return try { return try {
val targetName = request.targetName?.toName() ?: Name.EMPTY val targetName = request.targetDevice?.toName() ?: Name.EMPTY
val device = this[targetName] ?: error("The device with name $targetName not found in $this") val device = this[targetName] ?: error("The device with name $targetName not found in $this")
DeviceController.respondMessage(device, targetName.toString(), request) DeviceController.respondMessage(device, targetName.toString(), request)
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.error(ex, request.action).respondsTo(request) DeviceMessage.error(ex, sourceDevice = request.targetDevice, targetDevice = request.sourceDevice)
} }
} }

View File

@ -1,8 +1,10 @@
package hep.dataforge.control.controllers package hep.dataforge.control.controllers
import hep.dataforge.control.api.* import hep.dataforge.control.api.DeviceHub
import hep.dataforge.control.api.DeviceListener
import hep.dataforge.control.api.get
import hep.dataforge.control.messages.DeviceMessage import hep.dataforge.control.messages.DeviceMessage
import hep.dataforge.control.messages.respondsTo import hep.dataforge.control.messages.PropertyChangedMessage
import hep.dataforge.control.messages.toEnvelope import hep.dataforge.control.messages.toEnvelope
import hep.dataforge.io.Consumer import hep.dataforge.io.Consumer
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
@ -48,13 +50,11 @@ public class HubController(
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) { override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
if (value == null) return if (value == null) return
scope.launch { scope.launch {
val change = DeviceMessage( val change = PropertyChangedMessage(
sourceName = name.toString(), sourceDevice = name.toString(),
action = DeviceMessage.PROPERTY_CHANGED_ACTION,
key = propertyName, key = propertyName,
value = value value = value
) )
messageOutbox.send(change) messageOutbox.send(change)
} }
} }
@ -64,23 +64,24 @@ public class HubController(
} }
public suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try { public suspend fun respondMessage(message: DeviceMessage): DeviceMessage = try {
val targetName = message.targetName?.toName() ?: Name.EMPTY val targetName = message.targetDevice?.toName() ?: Name.EMPTY
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub") val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
DeviceController.respondMessage(device, targetName.toString(), message) DeviceController.respondMessage(device, targetName.toString(), message)
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.error(ex, message.action).respondsTo(message) DeviceMessage.error(ex, sourceDevice = null, targetDevice = message.sourceDevice)
} }
override suspend fun respond(request: Envelope): Envelope = try { override suspend fun respond(request: Envelope): Envelope = try {
val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY val targetName = request.meta[DeviceMessage.TARGET_KEY].string?.toName() ?: Name.EMPTY
val device = hub[targetName] ?: error("The device with name $targetName not found in $hub") val device = hub[targetName] ?: error("The device with name $targetName not found in $hub")
if (request.data == null) { if (request.data == null) {
DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.fromMeta(request.meta)).toEnvelope() DeviceController.respondMessage(device, targetName.toString(), DeviceMessage.fromMeta(request.meta))
.toEnvelope()
} else { } else {
DeviceController.respond(device, targetName.toString(), request) DeviceController.respond(device, targetName.toString(), request)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
DeviceMessage.error(ex).toEnvelope() DeviceMessage.error(ex, sourceDevice = null).toEnvelope()
} }
override fun consume(message: Envelope) { override fun consume(message: Envelope) {

View File

@ -9,8 +9,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* Flow changes of all properties of a given device ignoring invalidation events
*/
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
public suspend fun Device.flowValues(): Flow<Pair<String, MetaItem<*>>> = callbackFlow { public suspend fun Device.flowPropertyChanges(): Flow<Pair<String, MetaItem<*>>> = callbackFlow {
val listener = object : DeviceListener { val listener = object : DeviceListener {
override fun propertyChanged(propertyName: String, value: MetaItem<*>?) { override fun propertyChanged(propertyName: String, value: MetaItem<*>?) {
if (value != null) { if (value != null) {

View File

@ -12,24 +12,19 @@ import kotlinx.serialization.json.encodeToJsonElement
@Serializable @Serializable
public sealed class DeviceMessage{ public sealed class DeviceMessage{
public abstract val sourceName: String? public abstract val sourceDevice: String?
public abstract val targetName: String? public abstract val targetDevice: String?
public abstract val comment: String? public abstract val comment: String?
public companion object { public companion object {
public val SOURCE_KEY: Name = DeviceMessage::sourceName.name.asName() public val SOURCE_KEY: Name = DeviceMessage::sourceDevice.name.asName()
public val TARGET_KEY: Name = DeviceMessage::targetName.name.asName() public val TARGET_KEY: Name = DeviceMessage::targetDevice.name.asName()
public val MESSAGE_ACTION_KEY: Name = DeviceMessage::action.name.asName()
public val MESSAGE_KEY_KEY: Name = DeviceMessage::key.name.asName()
public val MESSAGE_VALUE_KEY: Name = DeviceMessage::value.name.asName()
public const val OK_STATUS: String = "OK"
public const val FAIL_STATUS: String = "FAIL"
public const val PROPERTY_CHANGED_ACTION: String = "event.propertyChanged"
public fun error( public fun error(
cause: Throwable, cause: Throwable,
sourceDevice: String?,
targetDevice: String? = null,
): DeviceErrorMessage = DeviceErrorMessage( ): DeviceErrorMessage = DeviceErrorMessage(
errorMessage = cause.message, errorMessage = cause.message,
errorType = cause::class.simpleName, errorType = cause::class.simpleName,
@ -45,27 +40,50 @@ public sealed class DeviceMessage{
public data class PropertyChangedMessage( public data class PropertyChangedMessage(
public val key: String, public val key: String,
public val value: MetaItem<*>?, public val value: MetaItem<*>?,
override val sourceName: String? = null, override val sourceDevice: String,
override val targetName: String? = null, override val targetDevice: String? = null,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()
@Serializable @Serializable
@SerialName("property.set") @SerialName("property.set")
public data class PropertySetMessage( public data class PropertySetMessage(
public val key: String, public val property: String,
public val value: MetaItem<*>, public val value: MetaItem<*>?,
override val sourceName: String? = null, override val sourceDevice: String? = null,
override val targetName: String? = null, override val targetDevice: String,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()
@Serializable @Serializable
@SerialName("property.read") @SerialName("property.get")
public data class PropertyReadMessage( public data class PropertyGetMessage(
public val key: String, public val property: String,
override val sourceName: String? = null, override val sourceDevice: String? = null,
override val targetName: String? = null, override val targetDevice: String,
override val comment: String? = null,
) : DeviceMessage()
/**
* Request device description
*/
@Serializable
@SerialName("description.get")
public data class GetDescriptionMessage(
override val sourceDevice: String? = null,
override val targetDevice: String,
override val comment: String? = null,
) : DeviceMessage()
/**
* The full device description message
*/
@Serializable
@SerialName("description")
public data class DescriptionMessage(
val description: Meta,
override val sourceDevice: String,
override val targetDevice: String? = null,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()
@ -74,8 +92,8 @@ public data class PropertyReadMessage(
public data class ActionExecuteMessage( public data class ActionExecuteMessage(
public val action: String, public val action: String,
public val argument: MetaItem<*>?, public val argument: MetaItem<*>?,
override val sourceName: String? = null, override val sourceDevice: String? = null,
override val targetName: String? = null, override val targetDevice: String? = null,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()
@ -84,19 +102,46 @@ public data class ActionExecuteMessage(
public data class ActionResultMessage( public data class ActionResultMessage(
public val action: String, public val action: String,
public val result: MetaItem<*>?, public val result: MetaItem<*>?,
override val sourceName: String? = null, override val sourceDevice: String? = null,
override val targetName: String? = null, override val targetDevice: String? = null,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()
/**
* Notifies listeners that a new binary with given [binaryID] is available. The binary itself could not be provided via [DeviceMessage] API.
*/
@Serializable
@SerialName("binary.notification")
public data class BinaryNotificationMessage(
val binaryID: String,
override val sourceDevice: String,
override val targetDevice: String? = null,
override val comment: String? = null,
) : DeviceMessage()
/**
* The message states that the message is received, but no meaningful response is produced.
* This message could be used for a heartbeat.
*/
@Serializable
@SerialName("empty")
public data class EmptyDeviceMessage(
override val sourceDevice: String? = null,
override val targetDevice: String? = null,
override val comment: String? = null,
) : DeviceMessage()
/**
* The evaluation of the message produced a service error
*/
@Serializable @Serializable
@SerialName("error") @SerialName("error")
public data class DeviceErrorMessage( public data class DeviceErrorMessage(
public val errorMessage: String?, public val errorMessage: String?,
public val errorType: String? = null, public val errorType: String? = null,
public val errorStackTrace: String? = null, public val errorStackTrace: String? = null,
override val sourceName: String? = null, override val sourceDevice: String? = null,
override val targetName: String? = null, override val targetDevice: String? = null,
override val comment: String? = null, override val comment: String? = null,
) : DeviceMessage() ) : DeviceMessage()

View File

@ -30,15 +30,6 @@ internal suspend fun ApplicationCall.respondJson(builder: JsonObjectBuilder.() -
respondText(json.toString(), contentType = ContentType.Application.Json) respondText(json.toString(), contentType = ContentType.Application.Json)
} }
public suspend fun ApplicationCall.respondMessage(message: DeviceMessage) { public suspend fun ApplicationCall.respondMessage(message: DeviceMessage) {
respondText(Json.encodeToString(MetaSerializer, message.toMeta()), contentType = ContentType.Application.Json) respondText(Json.encodeToString(MetaSerializer, message.toMeta()), contentType = ContentType.Application.Json)
} }
//public suspend fun ApplicationCall.respondMessage(builder: DeviceMessage.() -> Unit) {
// respondMessage(DeviceMessage(builder))
//}
//
//public suspend fun ApplicationCall.respondFail(builder: DeviceMessage.() -> Unit) {
// respondMessage(DeviceMessage.fail(null, block = builder))
//}

View File

@ -4,11 +4,11 @@ package hep.dataforge.control.server
import hep.dataforge.control.api.get import hep.dataforge.control.api.get
import hep.dataforge.control.controllers.DeviceController.Companion.GET_PROPERTY_ACTION
import hep.dataforge.control.controllers.DeviceController.Companion.SET_PROPERTY_ACTION
import hep.dataforge.control.controllers.DeviceManager import hep.dataforge.control.controllers.DeviceManager
import hep.dataforge.control.controllers.respondMessage import hep.dataforge.control.controllers.respondMessage
import hep.dataforge.control.messages.DeviceMessage import hep.dataforge.control.messages.DeviceMessage
import hep.dataforge.control.messages.PropertyGetMessage
import hep.dataforge.control.messages.PropertySetMessage
import hep.dataforge.meta.toJson import hep.dataforge.meta.toJson
import hep.dataforge.meta.toMeta import hep.dataforge.meta.toMeta
import hep.dataforge.meta.toMetaItem import hep.dataforge.meta.toMetaItem
@ -201,11 +201,10 @@ public fun Application.deviceModule(
get("get") { get("get") {
val target: String by call.parameters val target: String by call.parameters
val property: String by call.parameters val property: String by call.parameters
val request = DeviceMessage( val request = PropertyGetMessage(
action = GET_PROPERTY_ACTION, sourceDevice = WEB_SERVER_TARGET,
sourceName = WEB_SERVER_TARGET, targetDevice = target,
targetName = target, property = property,
key = property,
) )
val response = manager.respondMessage(request) val response = manager.respondMessage(request)
@ -217,11 +216,10 @@ public fun Application.deviceModule(
val body = call.receiveText() val body = call.receiveText()
val json = Json.parseToJsonElement(body) val json = Json.parseToJsonElement(body)
val request = DeviceMessage( val request = PropertySetMessage(
action = SET_PROPERTY_ACTION, sourceDevice = WEB_SERVER_TARGET,
sourceName = WEB_SERVER_TARGET, targetDevice = target,
targetName = target, property = property,
key = property,
value = json.toMetaItem() value = json.toMetaItem()
) )

View File

@ -5,6 +5,12 @@ plugins {
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
kscience{
useSerialization {
json()
}
}
kotlin { kotlin {
sourceSets { sourceSets {
commonMain { commonMain {

View File

@ -22,7 +22,7 @@ dependencies{
implementation(project(":dataforge-magix-client")) implementation(project(":dataforge-magix-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.3.0-dev-2") implementation("kscience.plotlykt:plotlykt-server:0.3.0")
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -6,8 +6,8 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
public data class MagixMessageFilter( public data class MagixMessageFilter(
val format: List<String>? = null, val format: List<String?>? = null,
val origin: List<String>? = null, val origin: List<String?>? = null,
val target: List<String?>? = null, val target: List<String?>? = null,
val action: List<String?>? = null, val action: List<String?>? = null,
) { ) {

View File

@ -4,36 +4,57 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
public interface MagixProcessor { public fun interface MagixProcessor {
public fun process(endpoint: MagixEndpoint): Job public fun process(endpoint: MagixEndpoint): Job
}
/** public companion object {
/**
* A converter from one (or several) format to another. It captures all events with the given filter then transforms it * A converter from one (or several) format to another. It captures all events with the given filter then transforms it
* with given [transformer] and sends back to the loop with given [outputFormat]. * with given [transformer] and sends back to the loop with given [outputFormat].
* *
* If [newOrigin] is not null, it is used as a replacement for old [MagixMessage.origin] tag. * If [newOrigin] is not null, it is used as a replacement for old [MagixMessage.origin] tag.
*/ */
public class MagixConverter( public fun <T : Any, R : Any> convert(
private val scope: CoroutineScope, scope: CoroutineScope,
private val filter: MagixMessageFilter, filter: MagixMessageFilter,
private val outputFormat: String, outputFormat: String,
private val newOrigin: String? = null, inputSerializer: KSerializer<T>,
private val transformer: suspend (JsonElement) -> JsonElement, outputSerializer: KSerializer<R>,
) : MagixProcessor { newOrigin: String? = null,
override fun process(endpoint: MagixEndpoint): Job = scope.launch { transformer: suspend (T) -> R,
endpoint.subscribe(filter).onEach { message -> ): MagixProcessor = MagixProcessor { endpoint ->
endpoint.subscribe(inputSerializer, filter).onEach { message ->
val newPayload = transformer(message.payload) val newPayload = transformer(message.payload)
val transformed = message.copy( val transformed: MagixMessage<R> = MagixMessage(
payload = newPayload, outputFormat,
format = outputFormat, newOrigin ?: message.origin,
origin = newOrigin ?: message.origin newPayload,
message.target,
message.id,
message.parentId,
message.user
) )
endpoint.broadcast(transformed) endpoint.broadcast(outputSerializer, transformed)
}.launchIn(this) }.launchIn(scope)
//TODO add catch logic here
} }
}
public fun convert(
scope: CoroutineScope,
filter: MagixMessageFilter,
outputFormat: String,
newOrigin: String? = null,
transformer: suspend (JsonElement) -> JsonElement,
): MagixProcessor = convert(
scope,
filter,
outputFormat,
JsonElement.serializer(),
JsonElement.serializer(),
newOrigin,
transformer
)
} }

View File

@ -22,8 +22,8 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
public class RSocketMagixEndpoint( public class RSocketMagixEndpoint(
val coroutineContext: CoroutineContext, private val coroutineContext: CoroutineContext,
public val rSocket: RSocket, private val rSocket: RSocket,
) : MagixEndpoint { ) : MagixEndpoint {
override fun <T> subscribe( override fun <T> subscribe(

View File

@ -1,5 +1,3 @@
import ru.mipt.npm.gradle.useFx
plugins { plugins {
id("ru.mipt.npm.jvm") id("ru.mipt.npm.jvm")
id("ru.mipt.npm.publish") id("ru.mipt.npm.publish")
@ -9,11 +7,14 @@ plugins {
//TODO to be moved to a separate project //TODO to be moved to a separate project
application{ application{
mainClassName = "ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt" mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt")
} }
kotlin{ kotlin{
explicitApi = null explicitApi = null
}
kscience{
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS, configuration = ru.mipt.npm.gradle.DependencyConfiguration.IMPLEMENTATION) useFx(ru.mipt.npm.gradle.FXModule.CONTROLS, configuration = ru.mipt.npm.gradle.DependencyConfiguration.IMPLEMENTATION)
} }

View File

@ -1,6 +1,6 @@
pluginManagement { pluginManagement {
val kotlinVersion = "1.4.20-M2" val kotlinVersion = "1.4.20"
val toolsVersion = "0.6.4-dev-1.4.20-M2" val toolsVersion = "0.7.0"
repositories { repositories {
mavenLocal() mavenLocal()