allows scrolling backwards through history
This commit is contained in:
@@ -3,6 +3,7 @@ import { computed, inject, provide, ref, toValue, watch } from 'vue';
|
|||||||
import { AXIS_DATA, type AxisData, AxisType } from '@/graph/axis';
|
import { AXIS_DATA, type AxisData, AxisType } from '@/graph/axis';
|
||||||
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||||
import NumericText from '@/components/NumericText.vue';
|
import NumericText from '@/components/NumericText.vue';
|
||||||
|
import { useNow } from '@/composables/ticker';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
y_limits?: [number, number];
|
y_limits?: [number, number];
|
||||||
@@ -28,7 +29,9 @@ const raw_max_y = ref(-Infinity);
|
|||||||
|
|
||||||
const axis_update_watch = ref(0);
|
const axis_update_watch = ref(0);
|
||||||
|
|
||||||
watch([graph_data.min_x, graph_data.max_x], () => {
|
const axis_update_ticker = useNow(50);
|
||||||
|
|
||||||
|
watch([graph_data.min_x, graph_data.max_x, axis_update_ticker], () => {
|
||||||
axis_update_watch.value++;
|
axis_update_watch.value++;
|
||||||
min_y.value = raw_min_y.value;
|
min_y.value = raw_min_y.value;
|
||||||
max_y.value = raw_max_y.value;
|
max_y.value = raw_max_y.value;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useNow } from '@/composables/ticker';
|
import { useNow } from '@/composables/ticker';
|
||||||
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||||
import TimeText from '@/components/TimeText.vue';
|
import TimeText from '@/components/TimeText.vue';
|
||||||
|
import { getDateString } from '@/datetime';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
duration?: number;
|
duration?: number;
|
||||||
@@ -107,11 +108,47 @@ const border_right = computed(
|
|||||||
const border_top = computed(() => 6);
|
const border_top = computed(() => 6);
|
||||||
const border_bottom = computed(() => (props.hide_time_labels ? 6 : 24));
|
const border_bottom = computed(() => (props.hide_time_labels ? 6 : 24));
|
||||||
|
|
||||||
const max_x = now;
|
const max_x = ref(now.value);
|
||||||
const min_x = computed(() => max_x.value - window_duration.value);
|
const min_x = computed(() => max_x.value - window_duration.value);
|
||||||
|
|
||||||
|
const fetch_history = ref(0);
|
||||||
|
|
||||||
const diff_x = computed(() => max_x.value - min_x.value);
|
const diff_x = computed(() => max_x.value - min_x.value);
|
||||||
|
|
||||||
|
const live = ref(true);
|
||||||
|
watch([live], ([live_value]) => {
|
||||||
|
if (live_value) {
|
||||||
|
fetch_history.value++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const valid_max_x_text = ref(true);
|
||||||
|
watch([now], ([now_value]) => {
|
||||||
|
if (live.value) {
|
||||||
|
max_x.value = now_value;
|
||||||
|
valid_max_x_text.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const max_x_text = computed({
|
||||||
|
// getter
|
||||||
|
get() {
|
||||||
|
return getDateString(new Date(max_x.value), props.utc, true);
|
||||||
|
},
|
||||||
|
// setter
|
||||||
|
set(newValue) {
|
||||||
|
const new_max_x = Date.parse(
|
||||||
|
newValue.replace('/', '-').replace('/', '-').replace(' ', 'T'),
|
||||||
|
);
|
||||||
|
if (!Number.isNaN(new_max_x)) {
|
||||||
|
fetch_history.value++;
|
||||||
|
max_x.value = new_max_x;
|
||||||
|
valid_max_x_text.value = true;
|
||||||
|
} else {
|
||||||
|
valid_max_x_text.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const x_map = (x: number) => {
|
const x_map = (x: number) => {
|
||||||
return (
|
return (
|
||||||
((width.value - border_left.value - border_right.value) *
|
((width.value - border_left.value - border_right.value) *
|
||||||
@@ -203,7 +240,9 @@ const should_fade = ref(false);
|
|||||||
provide<GraphData>(GRAPH_DATA, {
|
provide<GraphData>(GRAPH_DATA, {
|
||||||
border_top: border_top,
|
border_top: border_top,
|
||||||
min_x: min_x,
|
min_x: min_x,
|
||||||
max_x: now,
|
max_x: max_x,
|
||||||
|
live: live,
|
||||||
|
fetch_history: fetch_history,
|
||||||
width: () =>
|
width: () =>
|
||||||
Math.max(width.value - border_left.value - border_right.value, 0),
|
Math.max(width.value - border_left.value - border_right.value, 0),
|
||||||
height: () =>
|
height: () =>
|
||||||
@@ -231,7 +270,17 @@ provide<GraphData>(GRAPH_DATA, {
|
|||||||
>
|
>
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<div>
|
<div>
|
||||||
<span>Duration Dropdown</span>
|
<input
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
:disabled="live"
|
||||||
|
:size="max_x_text.length"
|
||||||
|
v-model="max_x_text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" v-model="live" />
|
||||||
|
<label>Live</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const props = defineProps<{
|
|||||||
data: string;
|
data: string;
|
||||||
minimum_separation?: number;
|
minimum_separation?: number;
|
||||||
class?: string;
|
class?: string;
|
||||||
fetch_history?: number;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const smoothing_distance_x = 5;
|
const smoothing_distance_x = 5;
|
||||||
@@ -42,13 +41,17 @@ const min_sep = computed(() =>
|
|||||||
Math.min(props.minimum_separation || 0, maximum_minimum_separation_live),
|
Math.min(props.minimum_separation || 0, maximum_minimum_separation_live),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: telemetry_data } = useTelemetry(() => props.data);
|
|
||||||
const websocket = inject<ShallowRef<WebsocketHandle>>(WEBSOCKET_SYMBOL)!;
|
|
||||||
const value = websocket.value.listen_to_telemetry(telemetry_data, min_sep);
|
|
||||||
|
|
||||||
const graph_data = inject<GraphData>(GRAPH_DATA)!;
|
const graph_data = inject<GraphData>(GRAPH_DATA)!;
|
||||||
const axis_data = inject<AxisData>(AXIS_DATA)!;
|
const axis_data = inject<AxisData>(AXIS_DATA)!;
|
||||||
|
|
||||||
|
const { data: telemetry_data } = useTelemetry(() => props.data);
|
||||||
|
const websocket = inject<ShallowRef<WebsocketHandle>>(WEBSOCKET_SYMBOL)!;
|
||||||
|
const value = websocket.value.listen_to_telemetry(
|
||||||
|
telemetry_data,
|
||||||
|
min_sep,
|
||||||
|
graph_data.live,
|
||||||
|
);
|
||||||
|
|
||||||
const min = ref(Infinity);
|
const min = ref(Infinity);
|
||||||
const max = ref(-Infinity);
|
const max = ref(-Infinity);
|
||||||
|
|
||||||
@@ -104,8 +107,9 @@ watch([value], ([val]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const recompute_bounds = ref(0);
|
||||||
watch(
|
watch(
|
||||||
[telemetry_data, () => props.fetch_history],
|
[telemetry_data, () => toValue(graph_data.fetch_history)],
|
||||||
async ([data]) => {
|
async ([data]) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const uuid = data.uuid;
|
const uuid = data.uuid;
|
||||||
@@ -137,9 +141,10 @@ watch(
|
|||||||
);
|
);
|
||||||
triggerRef(memo);
|
triggerRef(memo);
|
||||||
debounced_recompute();
|
debounced_recompute();
|
||||||
|
recompute_bounds.value++;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Response?
|
// TODO: Response?
|
||||||
console.log(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -170,6 +175,10 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (memo_changed) {
|
if (memo_changed) {
|
||||||
|
recompute_bounds.value++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watch([recompute_bounds], () => {
|
||||||
let min_val = Infinity;
|
let min_val = Infinity;
|
||||||
let max_val = -Infinity;
|
let max_val = -Infinity;
|
||||||
for (let i = 1; i < memo.value.data.length; i++) {
|
for (let i = 1; i < memo.value.data.length; i++) {
|
||||||
@@ -181,7 +190,6 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => {
|
|||||||
debounced_recompute();
|
debounced_recompute();
|
||||||
max.value = max_val;
|
max.value = max_val;
|
||||||
min.value = min_val;
|
min.value = min_val;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -354,7 +362,7 @@ function onMouseExit(event: MouseEvent) {
|
|||||||
:cx="marker_radius"
|
:cx="marker_radius"
|
||||||
:cy="marker_radius"
|
:cy="marker_radius"
|
||||||
:r="marker_radius"
|
:r="marker_radius"
|
||||||
:class="`indexed-color color-${index}`"
|
:class="`indexed-color color-${index} marker`"
|
||||||
/>
|
/>
|
||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -446,6 +454,10 @@ function onMouseExit(event: MouseEvent) {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '@/assets/variables';
|
@use '@/assets/variables';
|
||||||
|
|
||||||
|
.fade rect.fade_other_selected {
|
||||||
|
opacity: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
.fade .fade_other_selected {
|
.fade .fade_other_selected {
|
||||||
opacity: 25%;
|
opacity: 25%;
|
||||||
}
|
}
|
||||||
@@ -463,6 +475,11 @@ function onMouseExit(event: MouseEvent) {
|
|||||||
fill: var(--indexed-color);
|
fill: var(--indexed-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
circle.marker {
|
||||||
|
stroke: variables.$background-color;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
polyline {
|
polyline {
|
||||||
stroke-width: 1px;
|
stroke-width: 1px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { getDateString } from '@/datetime';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
x: number;
|
x: number;
|
||||||
@@ -9,57 +10,12 @@ const props = defineProps<{
|
|||||||
show_millis?: boolean;
|
show_millis?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// This function is slow
|
|
||||||
function getDateString(date: Date) {
|
|
||||||
const year = props.utc ? date.getUTCFullYear() : date.getFullYear();
|
|
||||||
const month = (
|
|
||||||
(props.utc ? date.getMonth() : date.getMonth()) + 1
|
|
||||||
).toLocaleString('en-US', {
|
|
||||||
minimumIntegerDigits: 2,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
const day = (props.utc ? date.getUTCDate() : date.getDate()).toLocaleString(
|
|
||||||
'en-US',
|
|
||||||
{
|
|
||||||
minimumIntegerDigits: 2,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const hour = (
|
|
||||||
props.utc ? date.getUTCHours() : date.getHours()
|
|
||||||
).toLocaleString('en-US', {
|
|
||||||
minimumIntegerDigits: 2,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
const minute = (
|
|
||||||
props.utc ? date.getUTCMinutes() : date.getMinutes()
|
|
||||||
).toLocaleString('en-US', {
|
|
||||||
minimumIntegerDigits: 2,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
const second = (
|
|
||||||
props.utc ? date.getUTCSeconds() : date.getSeconds()
|
|
||||||
).toLocaleString('en-US', {
|
|
||||||
minimumIntegerDigits: 2,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
const milliseconds = (
|
|
||||||
props.utc ? date.getUTCMilliseconds() : date.getMilliseconds()
|
|
||||||
).toLocaleString('en-US', {
|
|
||||||
minimumIntegerDigits: 3,
|
|
||||||
useGrouping: false,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
return `${year}/${month}/${day} ${hour}:${minute}:${second}${props.show_millis ? `.${milliseconds}` : ''}${props.utc ? 'Z' : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timetext = computed(() => {
|
const timetext = computed(() => {
|
||||||
return getDateString(new Date(props.timestamp));
|
return getDateString(
|
||||||
|
new Date(props.timestamp),
|
||||||
|
props.utc,
|
||||||
|
props.show_millis,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
type MaybeRefOrGetter,
|
type MaybeRefOrGetter,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
onWatcherCleanup,
|
||||||
ref,
|
ref,
|
||||||
type Ref,
|
type Ref,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
@@ -95,6 +96,7 @@ export class WebsocketHandle {
|
|||||||
listen_to_telemetry(
|
listen_to_telemetry(
|
||||||
telemetry: MaybeRefOrGetter<TelemetryDefinition | null>,
|
telemetry: MaybeRefOrGetter<TelemetryDefinition | null>,
|
||||||
minimum_separation_ms: MaybeRefOrGetter<number> | undefined,
|
minimum_separation_ms: MaybeRefOrGetter<number> | undefined,
|
||||||
|
live: MaybeRefOrGetter<boolean>,
|
||||||
) {
|
) {
|
||||||
const value_result = ref<TelemetryDataItem | null>(null);
|
const value_result = ref<TelemetryDataItem | null>(null);
|
||||||
|
|
||||||
@@ -114,10 +116,12 @@ export class WebsocketHandle {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const is_live = computed(() => toValue(live));
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[uuid, this.connected, minimum_separation],
|
[uuid, this.connected, minimum_separation, is_live],
|
||||||
([uuid_value, connected, min_sep]) => {
|
([uuid_value, connected, min_sep, live_value]) => {
|
||||||
if (connected && uuid_value) {
|
if (connected && uuid_value && live_value) {
|
||||||
this.websocket?.send(
|
this.websocket?.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
RegisterTlmListener: {
|
RegisterTlmListener: {
|
||||||
@@ -129,8 +133,26 @@ export class WebsocketHandle {
|
|||||||
if (!this.on_telem_value.has(uuid_value)) {
|
if (!this.on_telem_value.has(uuid_value)) {
|
||||||
this.on_telem_value.set(uuid_value, []);
|
this.on_telem_value.set(uuid_value, []);
|
||||||
}
|
}
|
||||||
this.on_telem_value.get(uuid_value)?.push((value) => {
|
const callback_fn = (value: TelemetryDataItem) => {
|
||||||
value_result.value = value;
|
value_result.value = value;
|
||||||
|
};
|
||||||
|
this.on_telem_value.get(uuid_value)?.push(callback_fn);
|
||||||
|
onWatcherCleanup(() => {
|
||||||
|
this.websocket?.send(
|
||||||
|
JSON.stringify({
|
||||||
|
UnregisterTlmListener: {
|
||||||
|
uuid: uuid_value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const index = this.on_telem_value
|
||||||
|
.get(uuid_value)
|
||||||
|
?.indexOf(callback_fn);
|
||||||
|
if (index !== undefined && index >= 0) {
|
||||||
|
this.on_telem_value
|
||||||
|
.get(uuid_value)
|
||||||
|
?.splice(index, 1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
49
frontend/src/datetime.ts
Normal file
49
frontend/src/datetime.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// This function is slow
|
||||||
|
export function getDateString(date: Date, utc: boolean, millis: boolean) {
|
||||||
|
const year = utc ? date.getUTCFullYear() : date.getFullYear();
|
||||||
|
const month = (
|
||||||
|
(utc ? date.getMonth() : date.getMonth()) + 1
|
||||||
|
).toLocaleString('en-US', {
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
const day = (utc ? date.getUTCDate() : date.getDate()).toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const hour = (utc ? date.getUTCHours() : date.getHours()).toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const minute = (
|
||||||
|
utc ? date.getUTCMinutes() : date.getMinutes()
|
||||||
|
).toLocaleString('en-US', {
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
const second = (
|
||||||
|
utc ? date.getUTCSeconds() : date.getSeconds()
|
||||||
|
).toLocaleString('en-US', {
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
const milliseconds = (
|
||||||
|
utc ? date.getUTCMilliseconds() : date.getMilliseconds()
|
||||||
|
).toLocaleString('en-US', {
|
||||||
|
minimumIntegerDigits: 3,
|
||||||
|
useGrouping: false,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
return `${year}/${month}/${day} ${hour}:${minute}:${second}${millis ? `.${milliseconds}` : ''}${utc ? 'Z' : ''}`;
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ export interface GraphData {
|
|||||||
border_top: MaybeRefOrGetter<number>;
|
border_top: MaybeRefOrGetter<number>;
|
||||||
min_x: MaybeRefOrGetter<number>;
|
min_x: MaybeRefOrGetter<number>;
|
||||||
max_x: MaybeRefOrGetter<number>;
|
max_x: MaybeRefOrGetter<number>;
|
||||||
|
live: MaybeRefOrGetter<boolean>;
|
||||||
|
fetch_history: MaybeRefOrGetter<number>;
|
||||||
width: MaybeRefOrGetter<number>;
|
width: MaybeRefOrGetter<number>;
|
||||||
height: MaybeRefOrGetter<number>;
|
height: MaybeRefOrGetter<number>;
|
||||||
x_map: (x: number) => number;
|
x_map: (x: number) => number;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::http::api::setup_api;
|
|||||||
use crate::http::websocket::setup_websocket;
|
use crate::http::websocket::setup_websocket;
|
||||||
use crate::telemetry::management_service::TelemetryManagementService;
|
use crate::telemetry::management_service::TelemetryManagementService;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use log::{error, info};
|
use log::info;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
@@ -29,7 +29,5 @@ pub async fn setup(
|
|||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
error!("http setup end");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::http::websocket::request::{RegisterTlmListenerRequest, WebsocketRequest};
|
use std::collections::HashMap;
|
||||||
|
use crate::http::websocket::request::{RegisterTlmListenerRequest, UnregisterTlmListenerRequest, WebsocketRequest};
|
||||||
use crate::http::websocket::response::{TlmValueResponse, WebsocketResponse};
|
use crate::http::websocket::response::{TlmValueResponse, WebsocketResponse};
|
||||||
use crate::telemetry::management_service::TelemetryManagementService;
|
use crate::telemetry::management_service::TelemetryManagementService;
|
||||||
use actix_web::{rt, web, HttpRequest, HttpResponse};
|
use actix_web::{rt, web, HttpRequest, HttpResponse};
|
||||||
@@ -20,8 +21,13 @@ fn handle_register_tlm_listener(
|
|||||||
data: &Arc<TelemetryManagementService>,
|
data: &Arc<TelemetryManagementService>,
|
||||||
request: RegisterTlmListenerRequest,
|
request: RegisterTlmListenerRequest,
|
||||||
tx: &Sender<WebsocketResponse>,
|
tx: &Sender<WebsocketResponse>,
|
||||||
|
tlm_listeners: &mut HashMap<String, CancellationToken>,
|
||||||
) {
|
) {
|
||||||
if let Some(tlm_data) = data.get_by_uuid(&request.uuid) {
|
if let Some(tlm_data) = data.get_by_uuid(&request.uuid) {
|
||||||
|
let token = CancellationToken::new();
|
||||||
|
if let Some(token) = tlm_listeners.insert(tlm_data.definition.uuid.clone(), token.clone()) {
|
||||||
|
token.cancel();
|
||||||
|
}
|
||||||
let minimum_separation = Duration::from_millis(request.minimum_separation_ms as u64);
|
let minimum_separation = Duration::from_millis(request.minimum_separation_ms as u64);
|
||||||
let mut rx = tlm_data.data.subscribe();
|
let mut rx = tlm_data.data.subscribe();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
@@ -35,6 +41,9 @@ fn handle_register_tlm_listener(
|
|||||||
_ = tx.closed() => {
|
_ = tx.closed() => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
_ = token.cancelled() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
Ok(_) = rx.changed() => {
|
Ok(_) = rx.changed() => {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let value = {
|
let value = {
|
||||||
@@ -71,15 +80,28 @@ fn handle_register_tlm_listener(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_unregister_tlm_listener(
|
||||||
|
request: UnregisterTlmListenerRequest,
|
||||||
|
tlm_listeners: &mut HashMap<String, CancellationToken>,
|
||||||
|
) {
|
||||||
|
if let Some(token) = tlm_listeners.remove(&request.uuid) {
|
||||||
|
token.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_websocket_message(
|
async fn handle_websocket_message(
|
||||||
data: &Arc<TelemetryManagementService>,
|
data: &Arc<TelemetryManagementService>,
|
||||||
request: WebsocketRequest,
|
request: WebsocketRequest,
|
||||||
tx: &Sender<WebsocketResponse>,
|
tx: &Sender<WebsocketResponse>,
|
||||||
|
tlm_listeners: &mut HashMap<String, CancellationToken>,
|
||||||
) {
|
) {
|
||||||
match request {
|
match request {
|
||||||
WebsocketRequest::RegisterTlmListener(request) => {
|
WebsocketRequest::RegisterTlmListener(request) => {
|
||||||
handle_register_tlm_listener(data, request, tx)
|
handle_register_tlm_listener(data, request, tx, tlm_listeners)
|
||||||
}
|
},
|
||||||
|
WebsocketRequest::UnregisterTlmListener(request) => {
|
||||||
|
handle_unregister_tlm_listener(request, tlm_listeners)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,12 +127,13 @@ async fn handle_websocket_incoming(
|
|||||||
data: &Arc<TelemetryManagementService>,
|
data: &Arc<TelemetryManagementService>,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
tx: &Sender<WebsocketResponse>,
|
tx: &Sender<WebsocketResponse>,
|
||||||
|
tlm_listeners: &mut HashMap<String, CancellationToken>,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(AggregatedMessage::Close(_)) => Ok(false),
|
Ok(AggregatedMessage::Close(_)) => Ok(false),
|
||||||
Ok(AggregatedMessage::Text(msg)) => match serde_json::from_str::<WebsocketRequest>(&msg) {
|
Ok(AggregatedMessage::Text(msg)) => match serde_json::from_str::<WebsocketRequest>(&msg) {
|
||||||
Ok(request) => {
|
Ok(request) => {
|
||||||
handle_websocket_message(data, request, tx).await;
|
handle_websocket_message(data, request, tx, tlm_listeners).await;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
Err(err) => Err(anyhow!("JSON Deserialization Error Encountered {err}")),
|
Err(err) => Err(anyhow!("JSON Deserialization Error Encountered {err}")),
|
||||||
@@ -141,12 +164,13 @@ pub async fn websocket_connect(
|
|||||||
let cancel_token = cancel_token.get_ref().clone();
|
let cancel_token = cancel_token.get_ref().clone();
|
||||||
|
|
||||||
rt::spawn(async move {
|
rt::spawn(async move {
|
||||||
|
let mut tlm_listeners = HashMap::new();
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel::<WebsocketResponse>(128);
|
let (tx, mut rx) = tokio::sync::mpsc::channel::<WebsocketResponse>(128);
|
||||||
loop {
|
loop {
|
||||||
let result = select! {
|
let result = select! {
|
||||||
_ = cancel_token.cancelled() => Ok(false),
|
_ = cancel_token.cancelled() => Ok(false),
|
||||||
Some(msg) = rx.recv() => handle_websocket_response(msg, &mut session).await,
|
Some(msg) = rx.recv() => handle_websocket_response(msg, &mut session).await,
|
||||||
Some(msg) = stream.next() => handle_websocket_incoming(msg, data.get_ref(), &mut session, &tx).await,
|
Some(msg) = stream.next() => handle_websocket_incoming(msg, data.get_ref(), &mut session, &tx, &mut tlm_listeners).await,
|
||||||
else => Ok(false),
|
else => Ok(false),
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
@@ -160,6 +184,9 @@ pub async fn websocket_connect(
|
|||||||
}
|
}
|
||||||
rx.close();
|
rx.close();
|
||||||
let _ = session.close(None).await;
|
let _ = session.close(None).await;
|
||||||
|
for (_, token) in tlm_listeners.drain() {
|
||||||
|
token.cancel();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ pub struct RegisterTlmListenerRequest {
|
|||||||
pub minimum_separation_ms: u32,
|
pub minimum_separation_ms: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct UnregisterTlmListenerRequest {
|
||||||
|
pub uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, From)]
|
#[derive(Debug, Clone, Serialize, Deserialize, From)]
|
||||||
pub enum WebsocketRequest {
|
pub enum WebsocketRequest {
|
||||||
RegisterTlmListener(RegisterTlmListenerRequest),
|
RegisterTlmListener(RegisterTlmListenerRequest),
|
||||||
|
UnregisterTlmListener(UnregisterTlmListenerRequest),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ pub async fn setup() -> anyhow::Result<()> {
|
|||||||
grpc_server.await?; //grpc server is dropped
|
grpc_server.await?; //grpc server is dropped
|
||||||
drop(cancellation_token); // All cancellation tokens are now dropped
|
drop(cancellation_token); // All cancellation tokens are now dropped
|
||||||
|
|
||||||
error!("after awaits");
|
|
||||||
|
|
||||||
// Perform cleanup functions - at this point all servers have stopped and we can be sure that cleaning things up is safe
|
// Perform cleanup functions - at this point all servers have stopped and we can be sure that cleaning things up is safe
|
||||||
for _ in 0..15 {
|
for _ in 0..15 {
|
||||||
if Arc::strong_count(&tlm) != 1 {
|
if Arc::strong_count(&tlm) != 1 {
|
||||||
|
|||||||
Reference in New Issue
Block a user