Added a connection status element that shows current link usage and packet loss.

This commit is contained in:
RedNicStone committed 2026-11-09 18:17:42 +01:00
1 parent 26c29df4ad
commit 2d91487c6a
12 files changed
+415

No files matched your search

Binary file not shown.
+43
View File
@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://1sjxuy7kgxy7"
path="res://.godot/imported/link.svg-f8724c7cad57bde98ee0cda284f3ebbb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/vector/status/link.svg"
dest_files=["res://.godot/imported/link.svg-f8724c7cad57bde98ee0cda284f3ebbb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+103
View File
@@ -0,0 +1,103 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://scripts/link_panel.gd" id="1"]
[ext_resource type="PackedScene" path="res://components/link_rate.tscn" id="2"]
[ext_resource type="Texture2D" path="res://assets/vector/status/link.svg" id="3"]
[node name="LinkPanel" type="PanelContainer"]
custom_minimum_size = Vector2(360, 240)
offset_right = 360.0
offset_bottom = 240.0
theme_type_variation = &"VehicleBackground"
script = ExtResource("1")
link_icon = ExtResource("3")
[node name="Backdrop" type="Control" parent="."]
layout_mode = 2
clip_contents = true
mouse_filter = 2
[node name="LinkIcon" type="TextureRect" parent="Backdrop"]
unique_name_in_owner = true
layout_mode = 1
anchor_left = 0.65
anchor_right = 0.65
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -90.0
offset_right = 90.0
offset_top = -90.0
offset_bottom = 90.0
rotation = -0.20943952
pivot_offset = Vector2(90, 90)
mouse_filter = 2
texture = ExtResource("3")
expand_mode = 1
stretch_mode = 5
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_right = 16
theme_override_constants/margin_top = 10
theme_override_constants/margin_bottom = 10
[node name="Body" type="VBoxContainer" parent="Margin"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="Header" type="HBoxContainer" parent="Margin/Body"]
layout_mode = 2
[node name="Title" type="Label" parent="Margin/Body/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 18
text = "LINK"
[node name="State" type="Label" parent="Margin/Body/Header"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "NO TELEMETRY"
horizontal_alignment = 2
[node name="Rates" type="HBoxContainer" parent="Margin/Body"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 16
[node name="Downlink" parent="Margin/Body/Rates" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="Uplink" parent="Margin/Body/Rates" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
direction = "↑ UPLINK"
[node name="Loss" type="HBoxContainer" parent="Margin/Body"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="Title" type="Label" parent="Margin/Body/Loss"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "PACKET LOSS"
[node name="LossValue" type="Label" parent="Margin/Body/Loss"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "—"
[node name="LossWindow" type="Label" parent="Margin/Body/Loss"]
unique_name_in_owner = true
layout_mode = 2
theme_type_variation = &"LinkMuted"
theme_override_font_sizes/font_size = 14
text = "10 s window"
horizontal_alignment = 2
+57
View File
@@ -0,0 +1,57 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://scripts/link_rate.gd" id="1"]
[node name="LinkRate" type="Control"]
custom_minimum_size = Vector2(140, 148)
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("1")
[node name="Readouts" type="VBoxContainer" parent="."]
layout_mode = 1
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_right = 70.0
offset_top = -44.0
offset_bottom = 44.0
theme_override_constants/separation = 0
[node name="Direction" type="Label" parent="Readouts"]
unique_name_in_owner = true
layout_mode = 2
theme_type_variation = &"LinkRateNormal"
theme_override_font_sizes/font_size = 15
text = "↓ DOWNLINK"
horizontal_alignment = 1
[node name="Value" type="Label" parent="Readouts"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "—"
horizontal_alignment = 1
[node name="Units" type="Label" parent="Readouts"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "Gbps"
horizontal_alignment = 1
[node name="Capacity" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -62.0
offset_right = 62.0
offset_top = -23.0
offset_bottom = -3.0
theme_override_font_sizes/font_size = 14
text = "10 Gbps max"
horizontal_alignment = 1
+16
View File
@@ -0,0 +1,16 @@
# Link panel
Run `scenes/link_demo.tscn` with F6. The controls preview connected, near-capacity, saturated, degraded, disconnected, stale and missing telemetry, plus both themes.
```gdscript
$LinkPanel.set_telemetry(6.42, 1.08, 0.02, 0)
# down/up: Gbps; packet loss: percent; state: 0 connected, 1 degraded, 2 disconnected
```
Capacity defaults to 10 Gbps per direction. Arc and direction colors change at 80% and 95%; these thresholds and capacities are configurable. Numeric readings stay in the normal text color and remain unclamped above capacity. Utilization does not change the reported connection state.
`loss_window_seconds` describes the supplied loss measurement (default 10 s); compute that measurement upstream. Disconnected and missing telemetry show dashes. After `stale_after_seconds` (default 2), values are held with a STALE label. Invalid or non-finite samples are rejected. `clear_telemetry()` restores the missing-data state.
Layout and text are scene nodes; `link_rate.gd` only draws the utilization arcs. `link_icon` selects the subdued SVG watermark. The current local chain-link drawing is a draft. Colors come from the shared theme.
The panel replaces the Link placeholder in both layouts and the element library. Existing JSON layouts keep their `link` type IDs. Camera and telemetry transports are not connected by this prototype.
+50
View File
@@ -0,0 +1,50 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://scripts/link_demo.gd" id="1"]
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="2"]
[node name="LinkDemo" type="VBoxContainer"]
offset_left = 24.0
offset_top = 24.0
offset_right = 624.0
offset_bottom = 420.0
theme_override_constants/separation = 16
script = ExtResource("1")
[node name="Notice" type="Label" parent="."]
layout_mode = 2
text = "SIMULATED / LINK INSTRUMENT"
[node name="LinkPanel" parent="." instance=ExtResource("2")]
layout_mode = 2
size_flags_horizontal = 4
[node name="Controls" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="State" type="OptionButton" parent="Controls"]
layout_mode = 2
selected = 0
item_count = 7
popup/item_0/text = "Connected"
popup/item_0/id = 0
popup/item_1/text = "Near capacity"
popup/item_1/id = 1
popup/item_2/text = "Saturated"
popup/item_2/id = 2
popup/item_3/text = "Degraded"
popup/item_3/id = 3
popup/item_4/text = "Disconnected"
popup/item_4/id = 4
popup/item_5/text = "Stop feed / stale"
popup/item_5/id = 5
popup/item_6/text = "No telemetry"
popup/item_6/id = 6
[node name="Contrast" type="CheckButton" parent="Controls"]
layout_mode = 2
text = "High contrast"
[node name="Thresholds" type="Label" parent="."]
layout_mode = 2
text = "Arc thresholds: 80% warning · 95% saturation"
+22
View File
@@ -0,0 +1,22 @@
extends VBoxContainer
var elapsed := 0.0
@onready var panel = $LinkPanel
func _ready() -> void:
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
)
$Controls/State.item_selected.connect(func(index: int) -> void:
if index == 6: panel.clear_telemetry()
)
func _process(delta: float) -> void:
elapsed += delta
match $Controls/State.selected:
0: panel.set_telemetry(6.42 + sin(elapsed) * 0.12, 1.08, 0.02)
1: panel.set_telemetry(8.65, 7.9, 0.02)
2: panel.set_telemetry(9.72, 10.2, 0.02)
3: panel.set_telemetry(3.2, 0.4, 2.3, 1)
4: panel.set_telemetry(0, 0, 0, 2)
5, 6: pass
+1
View File
@@ -0,0 +1 @@
uid://i5dbrwr8d2gr
+74
View File
@@ -0,0 +1,74 @@
@tool
extends PanelContainer
enum Connection { CONNECTED, DEGRADED, DISCONNECTED }
@export var downlink_capacity_gbps := 10.0
@export var uplink_capacity_gbps := 10.0
@export_range(0, 1) var warning_fraction := 0.8
@export_range(0, 1) var fault_fraction := 0.95
@export var loss_window_seconds := 10.0
@export var stale_after_seconds := 2.0
@export var link_icon: Texture2D:
set(value):
link_icon = value
if is_node_ready():
%LinkIcon.texture = value
var _received_ms := -1
var _state := Connection.CONNECTED
var _down := 0.0
var _up := 0.0
var _loss := 0.0
var _stale := false
func _ready() -> void:
%LinkIcon.texture = link_icon
theme_changed.connect(_update_icon)
_update_icon()
_refresh()
## Rates in Gbps; loss in percent over loss_window_seconds.
func set_telemetry(down: float, up: float, loss_percent: float, state: int = Connection.CONNECTED) -> bool:
if not is_finite(down) or not is_finite(up) or not is_finite(loss_percent) or down < 0 or up < 0 or loss_percent < 0 or loss_percent > 100 or state < 0 or state > 2:
return false
_down = down
_up = up
_loss = loss_percent
_state = state
_received_ms = Time.get_ticks_msec()
_refresh()
return true
func clear_telemetry() -> void:
_received_ms = -1
_refresh()
func _process(_delta: float) -> void:
var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
if stale != _stale:
_refresh()
func _refresh() -> void:
if not is_node_ready():
return
var missing := _received_ms < 0
_stale = not missing and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
var measured := not missing and _state != Connection.DISCONNECTED
%State.text = "NO TELEMETRY" if missing else ("STALE" if _stale else ["CONNECTED", "DEGRADED", "DISCONNECTED"][_state])
%State.theme_type_variation = "LinkMuted" if missing else ("LinkRateWarning" if _stale or _state == Connection.DEGRADED else ("LinkRateFault" if _state == Connection.DISCONNECTED else "Label"))
var gauges := [%Downlink, %Uplink]
for index in range(2):
gauges[index].capacity_gbps = maxf([downlink_capacity_gbps, uplink_capacity_gbps][index], 0.001)
gauges[index].warning_fraction = warning_fraction
gauges[index].fault_fraction = maxf(warning_fraction, fault_fraction)
gauges[index].rate_gbps = [_down, _up][index]
gauges[index].available = measured
gauges[index].refresh()
%LossValue.text = "%.2f%%" % _loss if measured else "—"
%LossWindow.text = "%.0f s window" % loss_window_seconds + (" · held" if _stale else "")
func _update_icon() -> void:
var tint := get_theme_color("status_icon", "SurfaceSide")
tint.a = 0.06
%LinkIcon.self_modulate = tint
+1
View File
@@ -0,0 +1 @@
uid://84jt1vgxm2ei
+44
View File
@@ -0,0 +1,44 @@
@tool
extends Control
@export var direction := "↓ DOWNLINK"
var capacity_gbps := 10.0
var warning_fraction := 0.8
var fault_fraction := 0.95
var rate_gbps := 0.0
var available := false
func _ready() -> void:
resized.connect(queue_redraw)
theme_changed.connect(queue_redraw)
refresh()
func utilization() -> float:
return rate_gbps / maxf(capacity_gbps, 0.001)
func severity() -> int:
if not available:
return 0
return 2 if utilization() >= fault_fraction else (1 if utilization() >= warning_fraction else 0)
func refresh() -> void:
if not is_node_ready():
return
%Direction.text = direction
%Direction.theme_type_variation = "LinkMuted" if not available else ["LinkRateNormal", "LinkRateWarning", "LinkRateFault"][severity()]
%Value.text = "%.2f" % rate_gbps if available else "—"
%Capacity.text = "%s Gbps max" % String.num(capacity_gbps, 2)
queue_redraw()
func _draw() -> void:
var radius := maxf(1.0, minf(size.x, size.y) / 2 - 6)
var center := size / 2
var start := deg_to_rad(140)
var sweep := deg_to_rad(260)
draw_arc(center, radius, start, start + sweep, 96, get_theme_color("instrument_grid", "SurfaceSide"), 3, true)
for fraction in [warning_fraction, fault_fraction]:
var radial := Vector2.from_angle(start + sweep * fraction)
draw_line(center + radial * (radius - 4), center + radial * (radius + 4), get_theme_color("instrument_axis", "SurfaceSide"), 1, true)
if available and rate_gbps > 0:
var role: String = ["instrument_accent", "warning", "fault"][severity()]
draw_arc(center, radius, start, start + sweep * clampf(utilization(), 0, 1), 96, get_theme_color(role, "SurfaceSide"), 4, true)
+1
View File
@@ -0,0 +1 @@
uid://b1pcqygbaipjh