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