Fix visibility range for collective

This commit is contained in:
Alexander Nozik 2024-06-09 20:51:12 +03:00
parent e9bde68674
commit c55ce2cf9a
6 changed files with 89 additions and 56 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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