From 78a70765af2a6fa9f924c5e5e811ece55aef7fa2 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 15:17:20 +0300 Subject: [PATCH] feat(ludic.telemetry): objects and lists inside an event's properties, an item made of numbers, and a signature of what was written - all in the kept buffer Co-Authored-By: Claude Opus 5.5 --- packages/ludic.telemetry/index.ludic | 1 + packages/ludic.telemetry/props.ludic | 18 +++++++-- packages/ludic.telemetry/props_nest.ludic | 37 +++++++++++++++++++ packages/ludic.telemetry/state.ludic | 1 + .../ludic.telemetry/tests/ring_test.ludic | 29 +++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 packages/ludic.telemetry/props_nest.ludic diff --git a/packages/ludic.telemetry/index.ludic b/packages/ludic.telemetry/index.ludic index 8a69b357..8539fd02 100644 --- a/packages/ludic.telemetry/index.ludic +++ b/packages/ludic.telemetry/index.ludic @@ -7,6 +7,7 @@ import "state.ludic" import "facts.ludic" import "ports.ludic" import "props.ludic" +import "props_nest.ludic" import "stamp.ludic" import "ring.ludic" import "queue.ludic" diff --git a/packages/ludic.telemetry/props.ludic b/packages/ludic.telemetry/props.ludic index 6c95b219..19c4599e 100644 --- a/packages/ludic.telemetry/props.ludic +++ b/packages/ludic.telemetry/props.ludic @@ -4,7 +4,9 @@ export const TELEMETRY_LINE: int = 2048 # the longest line an event may be; a export property TelemetryProps { sb: StrBuf = null - n: int = 0 # properties written + n: int = 0 # properties written at the top + lv: []int = null # inside an object or a list: how many written at each level + d: int = 0 # how deep } # the writer, emptied, for the next event's properties @@ -12,6 +14,7 @@ export function telemetry_props(telemetry_st: mut TelemetryState) -> TelemetryPr let p = telemetry_st.telemetry__props sb_clear(p.sb) p.n = 0 + p.d = 0 return p } export function telemetry_str(p: TelemetryProps, k: string, s: string) -> void { @@ -27,10 +30,19 @@ export function telemetry_bool(p: TelemetryProps, k: string, b: bool) -> void { if b { sb_add(p.sb, "true") } else { sb_add(p.sb, "false") } } function tp_key(p: TelemetryProps, k: string) -> void { - if p.n > 0 { sb_byte(p.sb, 44) } + tp_next(p) tj_str(p.sb, k) sb_byte(p.sb, 58) - p.n += 1 +} +# a comma before all but the first at this level, and the count kept +function tp_next(p: TelemetryProps) -> void { + if p.d == 0 { + if p.n > 0 { sb_byte(p.sb, 44) } + p.n += 1 + return + } + if p.lv[p.d] > 0 { sb_byte(p.sb, 44) } + p.lv[p.d] += 1 } # a JSON string: quoted, a quote or a backslash escaped, a control byte as \u00XX diff --git a/packages/ludic.telemetry/props_nest.ludic b/packages/ludic.telemetry/props_nest.ludic new file mode 100644 index 00000000..8f37740f --- /dev/null +++ b/packages/ludic.telemetry/props_nest.ludic @@ -0,0 +1,37 @@ +# props_nest.ludic - an object or a list inside an event's properties ($set, a photograph's subjects, the +# party's names), written into the same kept buffer; and a signature of what was written, to tell a +# change without keeping a copy +export function telemetry_obj(p: TelemetryProps, k: string) -> void { tn_open(p, k, 123) } +export function telemetry_list(p: TelemetryProps, k: string) -> void { tn_open(p, k, 91) } +export function telemetry_obj_end(p: TelemetryProps) -> void { tn_close(p, 125) } +export function telemetry_list_end(p: TelemetryProps) -> void { tn_close(p, 93) } +function tn_open(p: TelemetryProps, k: string, c: int) -> void { + tp_key(p, k) + sb_byte(p.sb, c) + if p.lv == null { p.lv = words(8) } + if p.d < len(p.lv) - 1 { p.d += 1 } + p.lv[p.d] = 0 +} +function tn_close(p: TelemetryProps, c: int) -> void { + sb_byte(p.sb, c) + if p.d > 0 { p.d -= 1 } +} +# a string in the list being written +export function telemetry_item_str(p: TelemetryProps, s: string) -> void { + tp_next(p) + tj_str(p.sb, s) +} +# a string item made of numbers the caller writes itself (sb_int, sb_byte - no quote or backslash) +export function telemetry_item_open(p: TelemetryProps) -> StrBuf { + tp_next(p) + sb_byte(p.sb, 34) + return p.sb +} +export function telemetry_item_close(p: TelemetryProps) -> void { sb_byte(p.sb, 34) } + +# a number standing for what has been written: equal when the bytes are (FNV-1a, 31 bits) +export function telemetry_props_sig(p: TelemetryProps) -> int { + var h = 2166136261 & 2147483647 + for i in 0 .. sb_len(p.sb) { h = ((h ^ p.sb.b[i]) * 16777619) & 2147483647 } + return h +} diff --git a/packages/ludic.telemetry/state.ludic b/packages/ludic.telemetry/state.ludic index 70223bdb..2c3ee351 100644 --- a/packages/ludic.telemetry/state.ludic +++ b/packages/ludic.telemetry/state.ludic @@ -97,5 +97,6 @@ function telemetry__facts__new() -> Queue { return queue_new("tel function telemetry__props_new() -> TelemetryProps { let p = new TelemetryProps p.sb = sb_new(TELEMETRY_LINE) + p.lv = words(8) return p } diff --git a/packages/ludic.telemetry/tests/ring_test.ludic b/packages/ludic.telemetry/tests/ring_test.ludic index 5250cb7d..90e1956d 100644 --- a/packages/ludic.telemetry/tests/ring_test.ludic +++ b/packages/ludic.telemetry/tests/ring_test.ludic @@ -90,4 +90,33 @@ program RingTest { expect(Text.contains(telemetry_batch_body(telemetry_st, 4), "\"n\":3,")) expect(not Text.contains(telemetry_batch_body(telemetry_st, 4), "TELEMETRY_PLAYER")) } + + test "an object and a list inside the properties are JSON, and the signature tells a change" (telemetry_st: mut TelemetryState) { + start(telemetry_st, 50, 65536) + let p = telemetry_props(telemetry_st) + telemetry_int(p, "grade", 3) + telemetry_list(p, "subjects") + telemetry_item_str(p, "elk") + telemetry_item_str(p, "deer") + telemetry_list_end(p) + telemetry_obj(p, "$set") + telemetry_str(p, "arch", "arm64") + telemetry_list(p, "looks") + let sb = telemetry_item_open(p) + sb_int(sb, 4) + sb_byte(sb, 44) + sb_int(sb, 2) + telemetry_item_close(p) + telemetry_list_end(p) + telemetry_obj_end(p) + telemetry_bool(p, "last", false) + let sig = telemetry_props_sig(p) + telemetry_event(telemetry_st, "shot", p) + let a = telemetry_line(telemetry_st, 0) + expect(Text.contains(a, "{\"grade\":3,\"subjects\":[\"elk\",\"deer\"],\"$set\":{\"arch\":\"arm64\",\"looks\":[\"4,2\"]},\"last\":false,")) + expect(Json.parse(a) != null) + let q = telemetry_props(telemetry_st) + telemetry_int(q, "grade", 3) + expect(telemetry_props_sig(q) != sig) + } }