Fix visibility range for collective
This commit is contained in:
parent
e9bde68674
commit
c55ce2cf9a
@ -74,15 +74,16 @@ public fun <T, R> DeviceState.Companion.map(
|
||||
|
||||
public fun <T, R> DeviceState<T>.map(mapper: (T) -> R): DeviceStateWithDependencies<R> = DeviceState.map(this, mapper)
|
||||
|
||||
public fun DeviceState<NumericalValue<out UnitsOfMeasurement>>.values(): DeviceState<Double> = object : DeviceState<Double> {
|
||||
override val value: Double
|
||||
get() = this@values.value.value
|
||||
public fun DeviceState<NumericalValue<out UnitsOfMeasurement>>.values(): DeviceState<Double> =
|
||||
object : DeviceState<Double> {
|
||||
override val value: Double
|
||||
get() = this@values.value.value
|
||||
|
||||
override val valueFlow: Flow<Double>
|
||||
get() = this@values.valueFlow.map { it.value }
|
||||
override val valueFlow: Flow<Double>
|
||||
get() = this@values.valueFlow.map { it.value }
|
||||
|
||||
override fun toString(): String = this@values.toString()
|
||||
}
|
||||
override fun toString(): String = this@values.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine two device states into one read-only [DeviceState]. Only the latest value of each state is used.
|
||||
|
@ -41,9 +41,10 @@ private object InstantConverter : MetaConverter<Instant> {
|
||||
public val MetaConverter.Companion.instant: MetaConverter<Instant> get() = InstantConverter
|
||||
|
||||
private object DoubleRangeConverter : MetaConverter<ClosedFloatingPointRange<Double>> {
|
||||
override fun readOrNull(source: Meta): ClosedFloatingPointRange<Double>? = source.value?.doubleArray?.let { (start, end)->
|
||||
start..end
|
||||
}
|
||||
override fun readOrNull(source: Meta): ClosedFloatingPointRange<Double>? =
|
||||
source.value?.doubleArray?.let { (start, end) ->
|
||||
start..end
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
obj: ClosedFloatingPointRange<Double>,
|
||||
@ -51,3 +52,11 @@ private object DoubleRangeConverter : MetaConverter<ClosedFloatingPointRange<Dou
|
||||
}
|
||||
|
||||
public val MetaConverter.Companion.doubleRange: MetaConverter<ClosedFloatingPointRange<Double>> get() = DoubleRangeConverter
|
||||
|
||||
private object StringListConverter : MetaConverter<List<String>> {
|
||||
override fun convert(obj: List<String>): Meta = Meta(obj.map { it.asValue() }.asValue())
|
||||
|
||||
override fun readOrNull(source: Meta): List<String>? = source.stringList ?: source["@jsonArray"]?.stringList
|
||||
}
|
||||
|
||||
public val MetaConverter.Companion.stringList: MetaConverter<List<String>> get() = StringListConverter
|
||||
|
@ -19,10 +19,13 @@ import space.kscience.dataforge.meta.Meta
|
||||
public suspend fun <T> DeviceClient.read(propertySpec: DevicePropertySpec<*, T>): T =
|
||||
propertySpec.converter.readOrNull(readProperty(propertySpec.name)) ?: error("Property read result is not valid")
|
||||
|
||||
|
||||
public suspend fun <T> DeviceClient.request(propertySpec: DevicePropertySpec<*, T>): T =
|
||||
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
|
||||
|
||||
public fun <T> DeviceClient.getCached(propertySpec: DevicePropertySpec<*, T>): T? =
|
||||
getProperty(propertySpec.name)?.let { propertySpec.converter.read(it) }
|
||||
|
||||
|
||||
public suspend fun <T> DeviceClient.write(propertySpec: MutableDevicePropertySpec<*, T>, value: T) {
|
||||
writeProperty(propertySpec.name, propertySpec.converter.convert(value))
|
||||
}
|
||||
|
@ -3,12 +3,10 @@
|
||||
package space.kscience.controls.demo.collective
|
||||
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.constructor.DeviceConstructor
|
||||
import space.kscience.controls.constructor.MutableDeviceState
|
||||
import space.kscience.controls.constructor.registerAsProperty
|
||||
import space.kscience.controls.constructor.*
|
||||
import space.kscience.controls.misc.stringList
|
||||
import space.kscience.controls.peer.PeerConnection
|
||||
import space.kscience.controls.spec.DeviceSpec
|
||||
import space.kscience.controls.spec.unit
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
@ -56,9 +54,16 @@ interface CollectiveDevice : Device {
|
||||
write = { _, value -> setVelocity(value) }
|
||||
)
|
||||
|
||||
val listVisible by action(MetaConverter.unit, MetaConverter.valueList<String> { it.string }) {
|
||||
listVisible().toList()
|
||||
}
|
||||
val visibleNeighbors by property(
|
||||
MetaConverter.stringList,
|
||||
read = {
|
||||
listVisible().toList()
|
||||
}
|
||||
)
|
||||
|
||||
// val listVisible by action(MetaConverter.unit, MetaConverter.valueList<String> { it.string }) {
|
||||
// listVisible().toList()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,8 +79,28 @@ class CollectiveDeviceConstructor(
|
||||
|
||||
override val id: CollectiveDeviceId get() = configuration.deviceId
|
||||
|
||||
val position = registerAsProperty(CollectiveDevice.position, position.sample(configuration.reportInterval.milliseconds))
|
||||
val velocity = registerAsProperty(CollectiveDevice.velocity, velocity.sample(configuration.reportInterval.milliseconds))
|
||||
val position = registerAsProperty(
|
||||
CollectiveDevice.position,
|
||||
position.sample(configuration.reportInterval.milliseconds)
|
||||
)
|
||||
|
||||
val velocity = registerAsProperty(
|
||||
CollectiveDevice.velocity,
|
||||
velocity.sample(configuration.reportInterval.milliseconds)
|
||||
)
|
||||
|
||||
private val _visibleNeighbors: MutableDeviceState<Collection<CollectiveDeviceId>> = stateOf(emptyList())
|
||||
|
||||
val visibleNeighbors = registerAsProperty(
|
||||
CollectiveDevice.visibleNeighbors,
|
||||
_visibleNeighbors.map { it.toList() }
|
||||
)
|
||||
|
||||
init {
|
||||
position.onNext {
|
||||
_visibleNeighbors.value = observation.invoke().keys
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPosition(): Gmc = position.value
|
||||
|
||||
|
@ -20,8 +20,6 @@ import space.kscience.magix.api.MagixEndpoint
|
||||
import space.kscience.magix.rsocket.rSocketWithWebSockets
|
||||
import space.kscience.magix.server.startMagixServer
|
||||
import space.kscience.maps.coordinates.*
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
||||
internal data class CollectiveDeviceState(
|
||||
@ -46,9 +44,8 @@ internal fun VirtualDeviceState(
|
||||
internal class DeviceCollectiveModel(
|
||||
context: Context,
|
||||
val deviceStates: Collection<CollectiveDeviceState>,
|
||||
val visibilityRange: Distance = 0.4.kilometers,
|
||||
val visibilityRange: Distance = 1.kilometers,
|
||||
val radioRange: Distance = 5.kilometers,
|
||||
val reportInterval: Duration = 1000.milliseconds
|
||||
) : ModelConstructor(context), PeerConnection {
|
||||
|
||||
/**
|
||||
@ -107,6 +104,7 @@ internal fun CoroutineScope.launchCollectiveMagixServer(
|
||||
val server = startMagixServer(
|
||||
// RSocketMagixFlowPlugin()
|
||||
)
|
||||
val deviceEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
|
||||
|
||||
collectiveModel.devices.forEach { (id, device) ->
|
||||
val deviceContext = collectiveModel.context.buildContext(id.parseAsName()) {
|
||||
@ -116,7 +114,7 @@ internal fun CoroutineScope.launchCollectiveMagixServer(
|
||||
|
||||
deviceContext.install(id, device)
|
||||
|
||||
val deviceEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
|
||||
// val deviceEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
|
||||
|
||||
deviceContext.request(DeviceManager).launchMagixService(deviceEndpoint, id)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -24,9 +25,12 @@ import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
||||
import org.jetbrains.compose.splitpane.HorizontalSplitPane
|
||||
import org.jetbrains.compose.splitpane.rememberSplitPaneState
|
||||
@ -47,7 +51,6 @@ import space.kscience.maps.coordinates.Gmc
|
||||
import space.kscience.maps.coordinates.meters
|
||||
import space.kscience.maps.features.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
@Composable
|
||||
@ -55,6 +58,8 @@ fun rememberContext(name: String, contextBuilder: ContextBuilder.() -> Unit = {}
|
||||
Context(name, contextBuilder)
|
||||
}
|
||||
|
||||
private val gmcMetaConverter = MetaConverter.serializable<Gmc>()
|
||||
|
||||
@Composable
|
||||
fun App() {
|
||||
val scope = rememberCoroutineScope()
|
||||
@ -82,8 +87,7 @@ fun App() {
|
||||
val magixClient = MagixEndpoint.rSocketWithWebSockets("localhost")
|
||||
|
||||
client.complete(magixClient)
|
||||
|
||||
collectiveModel.roster.forEach { (id, config) ->
|
||||
collectiveModel.roster.forEach { (id, config) ->
|
||||
devices[id] = magixClient.remoteDevice(parentContext, "listener", id, id.parseAsName())
|
||||
}
|
||||
}
|
||||
@ -129,23 +133,14 @@ fun App() {
|
||||
client.await().subscribe(DeviceManager.magixFormat).onEach { (magixMessage, deviceMessage) ->
|
||||
if (deviceMessage is PropertyChangedMessage && deviceMessage.property == "position") {
|
||||
val id = magixMessage.sourceEndpoint
|
||||
val position = MetaConverter.serializable<Gmc>().read(deviceMessage.value)
|
||||
val position = gmcMetaConverter.read(deviceMessage.value)
|
||||
val activeDevice = selectedDeviceId?.let { devices[it] }
|
||||
|
||||
suspend fun DeviceClient.idIsVisible() = try {
|
||||
withTimeout(1.seconds) {
|
||||
id in execute(CollectiveDevice.listVisible)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
true
|
||||
}
|
||||
|
||||
if (
|
||||
activeDevice == null ||
|
||||
id == selectedDeviceId ||
|
||||
!showOnlyVisible ||
|
||||
activeDevice.idIsVisible()
|
||||
id in activeDevice.request(CollectiveDevice.visibleNeighbors)
|
||||
) {
|
||||
rectangle(
|
||||
position,
|
||||
@ -168,24 +163,29 @@ fun App() {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
devices.forEach { (id, _) ->
|
||||
collectiveModel.roster.forEach { (id, _) ->
|
||||
Card(
|
||||
elevation = 16.dp,
|
||||
modifier = Modifier.padding(8.dp).onClick {
|
||||
selectedDeviceId = id
|
||||
}.conditional(id == selectedDeviceId) {
|
||||
border(2.dp, Color.Blue)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = id,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth()
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (devices[id] == null) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
Text(
|
||||
text = id,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
if (id == selectedDeviceId) {
|
||||
roster[id]?.let {
|
||||
Text("Meta:", color = Color.Blue, fontWeight = FontWeight.Bold)
|
||||
@ -195,14 +195,11 @@ fun App() {
|
||||
}
|
||||
|
||||
currentPosition?.let { currentPosition ->
|
||||
Text("Широта: ${String.format("%.3f", currentPosition.latitude.toDegrees().value)}")
|
||||
Text(
|
||||
"Долгота: ${
|
||||
String.format(
|
||||
"%.3f",
|
||||
currentPosition.longitude.toDegrees().value
|
||||
)
|
||||
}"
|
||||
"Широта: ${String.format("%.3f", currentPosition.latitude.toDegrees().value)}"
|
||||
)
|
||||
Text(
|
||||
"Долгота: ${String.format("%.3f", currentPosition.longitude.toDegrees().value)}"
|
||||
)
|
||||
currentPosition.elevation?.let {
|
||||
Text("Высота: ${String.format("%.1f", it.meters)} м")
|
||||
|
Loading…
Reference in New Issue
Block a user