fix(ludic.telemetry): nothing made per event - properties and the line written into kept buffers, the queue a ring of bytes made at the start

An event was a tree of values encoded into a new string, a dropped line was never given back, and
every batch and save joined the queue into another. Now: telemetry_props / telemetry_str / _int /
_bool write the properties as JSON into a buffer the state keeps; the line is written beside it,
its time worked out from the clock's seconds (TelemetryWorld.clock_s, was stamp); its bytes go
into one ring (ring_bytes, at most queue_max lines), the oldest overwritten past either; a batch
and the file are written into a third kept buffer and go as bytes (TelemetryTransport.send takes
the bytes and their length; Http.body_bytes, Fs.write_bytes). The facts are pooled.
tests/ring_test: ten thousand events past the cap in lines and in bytes, 0 bytes of heap; the line
exact JSON, escaped, 2000-02-29 right; the batch puts the id in; the file round-trips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 15:15:12 +03:00
parent fc0f3d3790
commit d1bea7f10e
14 changed files with 454 additions and 97 deletions

View file

@ -0,0 +1,60 @@
# props.ludic - an event's properties written as JSON into a buffer the state keeps, never a tree of
# values: a game says an event often, and Ludic gives nothing back
export const TELEMETRY_LINE: int = 2048 # the longest line an event may be; a longer one is dropped
export property TelemetryProps {
sb: StrBuf = null
n: int = 0 # properties written
}
# the writer, emptied, for the next event's properties
export function telemetry_props(telemetry_st: mut TelemetryState) -> TelemetryProps {
let p = telemetry_st.telemetry__props
sb_clear(p.sb)
p.n = 0
return p
}
export function telemetry_str(p: TelemetryProps, k: string, s: string) -> void {
tp_key(p, k)
tj_str(p.sb, s)
}
export function telemetry_int(p: TelemetryProps, k: string, v: int) -> void {
tp_key(p, k)
sb_int(p.sb, v)
}
export function telemetry_bool(p: TelemetryProps, k: string, b: bool) -> void {
tp_key(p, k)
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) }
tj_str(p.sb, k)
sb_byte(p.sb, 58)
p.n += 1
}
# a JSON string: quoted, a quote or a backslash escaped, a control byte as \u00XX
function tj_str(sb: StrBuf, s: string) -> void {
sb_byte(sb, 34)
if s != null {
let sp: string = s
for i in 0 .. len(sp) {
let c = sp[i]
if c == 34 or c == 92 {
sb_byte(sb, 92)
sb_byte(sb, c)
} else if c < 32 {
sb_add(sb, "\\u00")
sb_byte(sb, tj_hex(c / 16))
sb_byte(sb, tj_hex(c % 16))
} else { sb_byte(sb, c) }
}
}
sb_byte(sb, 34)
}
function tj_hex(v: int) -> int {
if v < 10 { return 48 + v }
return 87 + v
}
# a line that reached the end of its buffer was cut: it is dropped rather than sent broken
function tj_full(sb: StrBuf) -> bool { return sb_len(sb) >= len(sb.b) - 1 }