wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3eda72e8c2
commit
5ffe50ed02
463 changed files with 72045 additions and 67421 deletions
|
|
@ -1,59 +1,63 @@
|
|||
# ident.ludic - one player id per machine: the operating system's own id (Windows MachineGuid,
|
||||
# macOS IOPlatformUUID, Linux /etc/machine-id) hashed with the game's salt, else random
|
||||
var telemetry__proc: int = -1
|
||||
var telemetry__proc_out: string = ""
|
||||
var telemetry__proc_t0: int = 0
|
||||
var telemetry__rand_n: int = 0
|
||||
|
||||
# n random bytes as hex. Crypto.random_hex reads /dev/urandom, which Windows lacks (zeros there),
|
||||
# so it is mixed with the clock and the process and hashed: distinct per start everywhere
|
||||
export function telemetry_random_hex(telemetry_st: mut TelemetryState, n: int) -> string {
|
||||
telemetry_st.telemetry__rand_n += 1
|
||||
let seed = Crypto.random_hex(n) + "|" + `{Time.now()}|{Crypto.random_u32()}|{Os.pid()}|{telemetry_st.telemetry__rand_n}`
|
||||
export function telemetry_random_hex(n: int) -> string {
|
||||
telemetry__rand_n += 1
|
||||
let seed = Crypto.random_hex(n) + "|" + `{Time.now()}|{Crypto.random_u32()}|{Os.pid()}|{telemetry__rand_n}`
|
||||
return Crypto.sha256(seed)[0..n * 2]
|
||||
}
|
||||
|
||||
# the id outright (a game with accounts of its own, a test); written to the id file
|
||||
export function telemetry_set_id(base_st: mut BaseState, telemetry_st: mut TelemetryState, id: string) -> void {
|
||||
telemetry_st.telemetry__id = id
|
||||
let path = telemetry__conf(telemetry_st).id_file
|
||||
export function telemetry_set_id(id: string) -> void {
|
||||
telemetry__id = id
|
||||
let path = telemetry__conf().id_file
|
||||
if len(path) > 0 {
|
||||
var v = telemetry__read_obj(path)
|
||||
if v == null { v = Value.object() }
|
||||
Value.put(v, "id", Value.str(id))
|
||||
Fs.write_text(path, Json.encode(v))
|
||||
}
|
||||
telemetry__fact(base_st, telemetry_st, TELEMETRY_ID, 0, 0, 0)
|
||||
telemetry__fact(TELEMETRY_ID, 0, 0, 0)
|
||||
}
|
||||
|
||||
function telemetry__random_id(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void { telemetry_set_id(base_st, telemetry_st, "r-" + telemetry_random_hex(telemetry_st, 16)) }
|
||||
function telemetry__random_id() -> void { telemetry_set_id("r-" + telemetry_random_hex(16)) }
|
||||
|
||||
function telemetry__from_machine(base_st: mut BaseState, telemetry_st: mut TelemetryState, raw: string) -> void {
|
||||
let salt = telemetry__conf(telemetry_st).id_salt
|
||||
telemetry_set_id(base_st, telemetry_st, "m-" + Crypto.sha256(salt + ":" + Text.lower(raw))[0..32])
|
||||
function telemetry__from_machine(raw: string) -> void {
|
||||
let salt = telemetry__conf().id_salt
|
||||
telemetry_set_id("m-" + Crypto.sha256(salt + ":" + Text.lower(raw))[0..32])
|
||||
}
|
||||
|
||||
function telemetry__id_begin(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
|
||||
if len(telemetry__conf(telemetry_st).id_salt) == 0 {
|
||||
telemetry__random_id(base_st, telemetry_st)
|
||||
function telemetry__id_begin() -> void {
|
||||
if len(telemetry__conf().id_salt) == 0 {
|
||||
telemetry__random_id()
|
||||
return
|
||||
}
|
||||
let plat = Os.platform()
|
||||
if plat == "linux" {
|
||||
let m = Fs.read_text("/etc/machine-id")
|
||||
if m != null and len(Text.trim(m)) > 0 { telemetry__from_machine(base_st, telemetry_st, Text.trim(m)) } else { telemetry__random_id(base_st, telemetry_st) }
|
||||
if m != null and len(Text.trim(m)) > 0 { telemetry__from_machine(Text.trim(m)) } else { telemetry__random_id() }
|
||||
return
|
||||
}
|
||||
telemetry_st.telemetry__proc_out = telemetry__conf(telemetry_st).id_scratch
|
||||
if len(telemetry_st.telemetry__proc_out) == 0 { telemetry_st.telemetry__proc_out = Os.temp_dir() + "/ludic-telemetry-machine.txt" }
|
||||
if Fs.exists(telemetry_st.telemetry__proc_out) { Fs.remove(telemetry_st.telemetry__proc_out) }
|
||||
telemetry_st.telemetry__proc = telemetry__spawn_machine(telemetry_st, plat)
|
||||
telemetry_st.telemetry__proc_t0 = Time.now()
|
||||
if telemetry_st.telemetry__proc < 0 { telemetry__random_id(base_st, telemetry_st) }
|
||||
telemetry__proc_out = telemetry__conf().id_scratch
|
||||
if len(telemetry__proc_out) == 0 { telemetry__proc_out = Os.temp_dir() + "/ludic-telemetry-machine.txt" }
|
||||
if Fs.exists(telemetry__proc_out) { Fs.remove(telemetry__proc_out) }
|
||||
telemetry__proc = telemetry__spawn_machine(plat)
|
||||
telemetry__proc_t0 = Time.now()
|
||||
if telemetry__proc < 0 { telemetry__random_id() }
|
||||
}
|
||||
|
||||
function telemetry__spawn_machine(telemetry_st: TelemetryState, plat: string) -> int {
|
||||
function telemetry__spawn_machine(plat: string) -> int {
|
||||
let args = new []string
|
||||
if plat == "windows" {
|
||||
var root = "C:\\Windows"
|
||||
if Os.has_env("SystemRoot") and len(Os.env("SystemRoot")) > 0 { root = Os.env("SystemRoot") }
|
||||
let out = "'" + Text.replace(telemetry_st.telemetry__proc_out, "'", "''") + "'"
|
||||
let out = "'" + Text.replace(telemetry__proc_out, "'", "''") + "'"
|
||||
push(args, "-NoProfile")
|
||||
push(args, "-NonInteractive")
|
||||
push(args, "-Command")
|
||||
|
|
@ -61,28 +65,28 @@ function telemetry__spawn_machine(telemetry_st: TelemetryState, plat: string) ->
|
|||
return Process.spawn(root + "\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", args)
|
||||
}
|
||||
push(args, "-c")
|
||||
push(args, "ioreg -rd1 -c IOPlatformExpertDevice | awk -F'\"' '/IOPlatformUUID/{print $4}' > '" + telemetry_st.telemetry__proc_out + "'")
|
||||
push(args, "ioreg -rd1 -c IOPlatformExpertDevice | awk -F'\"' '/IOPlatformUUID/{print $4}' > '" + telemetry__proc_out + "'")
|
||||
return Process.spawn("/bin/sh", args)
|
||||
}
|
||||
|
||||
# the machine id, once the child has written it (ten seconds at most)
|
||||
function telemetry__id_tick(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
|
||||
if telemetry_st.telemetry__proc < 0 { return }
|
||||
let code = Process.poll(telemetry_st.telemetry__proc)
|
||||
function telemetry__id_tick() -> void {
|
||||
if telemetry__proc < 0 { return }
|
||||
let code = Process.poll(telemetry__proc)
|
||||
if code < 0 {
|
||||
if Time.now() - telemetry_st.telemetry__proc_t0 > 10 {
|
||||
Process.kill(telemetry_st.telemetry__proc)
|
||||
Process.free(telemetry_st.telemetry__proc)
|
||||
telemetry_st.telemetry__proc = -1
|
||||
telemetry__random_id(base_st, telemetry_st)
|
||||
if Time.now() - telemetry__proc_t0 > 10 {
|
||||
Process.kill(telemetry__proc)
|
||||
Process.free(telemetry__proc)
|
||||
telemetry__proc = -1
|
||||
telemetry__random_id()
|
||||
}
|
||||
return
|
||||
}
|
||||
Process.free(telemetry_st.telemetry__proc)
|
||||
telemetry_st.telemetry__proc = -1
|
||||
Process.free(telemetry__proc)
|
||||
telemetry__proc = -1
|
||||
var raw = ""
|
||||
let s = Fs.read_text(telemetry_st.telemetry__proc_out)
|
||||
let s = Fs.read_text(telemetry__proc_out)
|
||||
if s != null { raw = Text.trim(s) }
|
||||
if Fs.exists(telemetry_st.telemetry__proc_out) { Fs.remove(telemetry_st.telemetry__proc_out) }
|
||||
if code == 0 and len(raw) >= 8 { telemetry__from_machine(base_st, telemetry_st, raw) } else { telemetry__random_id(base_st, telemetry_st) }
|
||||
if Fs.exists(telemetry__proc_out) { Fs.remove(telemetry__proc_out) }
|
||||
if code == 0 and len(raw) >= 8 { telemetry__from_machine(raw) } else { telemetry__random_id() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# ports.ludic - the config, and what the ports answer when the game leaves them unbound
|
||||
export function telemetry_config(telemetry_st: mut TelemetryState, c: TelemetryConfig) -> void { telemetry_st.telemetry__cfg = c }
|
||||
export function telemetry_config(c: TelemetryConfig) -> void { telemetry__cfg = c }
|
||||
|
||||
function telemetry__conf(telemetry_st: mut TelemetryState) -> TelemetryConfig {
|
||||
if telemetry_st.telemetry__cfg == null { telemetry_st.telemetry__cfg = new TelemetryConfig }
|
||||
return telemetry_st.telemetry__cfg
|
||||
function telemetry__conf() -> TelemetryConfig {
|
||||
if telemetry__cfg == null { telemetry__cfg = new TelemetryConfig }
|
||||
return telemetry__cfg
|
||||
}
|
||||
|
||||
function telemetry__yes() -> bool { return true }
|
||||
function telemetry__wall_ms(telemetry_st: TelemetryState) -> int { return (Time.now() - telemetry_st.telemetry__t0) * 1000 }
|
||||
function telemetry__wall_ms() -> int { return (Time.now() - telemetry__t0) * 1000 }
|
||||
function telemetry__wall_stamp() -> string { return DateTime.format(Time.now(), "YYYY-MM-DDTHH:mm:ssZ") }
|
||||
|
||||
function telemetry__can_send(telemetry_st: mut TelemetryState) -> bool {
|
||||
let c = telemetry__conf(telemetry_st)
|
||||
function telemetry__can_send() -> bool {
|
||||
let c = telemetry__conf()
|
||||
if len(c.host) == 0 or len(c.key) == 0 { return false }
|
||||
return TelemetryWorld.can_send()
|
||||
}
|
||||
|
|
@ -40,12 +40,12 @@ function telemetry__http_poll(h: int) -> int {
|
|||
|
||||
function telemetry__http_drop(h: int) -> void { Http.free(h) }
|
||||
|
||||
function telemetry__send(telemetry_st: mut TelemetryState, body: string) -> int { return TelemetryTransport.send(telemetry__conf(telemetry_st).host + telemetry__conf(telemetry_st).path, body) }
|
||||
function telemetry__send(body: string) -> int { return TelemetryTransport.send(telemetry__conf().host + telemetry__conf().path, body) }
|
||||
function telemetry__poll(h: int) -> int { return TelemetryTransport.poll(h) }
|
||||
|
||||
# a request abandoned (the player said no)
|
||||
function telemetry__drop_request(telemetry_st: mut TelemetryState) -> void {
|
||||
if telemetry_st.telemetry__h < 0 { return }
|
||||
TelemetryTransport.drop(telemetry_st.telemetry__h)
|
||||
telemetry_st.telemetry__h = -1
|
||||
function telemetry__drop_request() -> void {
|
||||
if telemetry__h < 0 { return }
|
||||
TelemetryTransport.drop(telemetry__h)
|
||||
telemetry__h = -1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
# queries.ludic - what the game (a Settings row, a test) asks, and a reset back to before start
|
||||
export function telemetry_id(telemetry_st: TelemetryState) -> string { return telemetry_st.telemetry__id }
|
||||
export function telemetry_session(telemetry_st: TelemetryState) -> string { return telemetry_st.telemetry__session }
|
||||
export function telemetry_ready(telemetry_st: TelemetryState) -> bool { return telemetry_st.telemetry__ready }
|
||||
export function telemetry_get_config(telemetry_st: mut TelemetryState) -> TelemetryConfig { return telemetry__conf(telemetry_st) }
|
||||
export function telemetry_fails(telemetry_st: TelemetryState) -> int { return telemetry_st.telemetry__fails }
|
||||
export function telemetry_next_ms(telemetry_st: TelemetryState) -> int { return telemetry_st.telemetry__next_ms }
|
||||
export function telemetry_in_flight(telemetry_st: TelemetryState) -> bool { return telemetry_st.telemetry__h >= 0 }
|
||||
export function telemetry_id() -> string { return telemetry__id }
|
||||
export function telemetry_session() -> string { return telemetry__session }
|
||||
export function telemetry_ready() -> bool { return telemetry__ready }
|
||||
export function telemetry_get_config() -> TelemetryConfig { return telemetry__conf() }
|
||||
export function telemetry_fails() -> int { return telemetry__fails }
|
||||
export function telemetry_next_ms() -> int { return telemetry__next_ms }
|
||||
export function telemetry_in_flight() -> bool { return telemetry__h >= 0 }
|
||||
|
||||
export function telemetry_queued(telemetry_st: TelemetryState) -> int {
|
||||
if telemetry_st.telemetry__q == null { return 0 }
|
||||
return len(telemetry_st.telemetry__q)
|
||||
export function telemetry_queued() -> int {
|
||||
if telemetry__q == null { return 0 }
|
||||
return len(telemetry__q)
|
||||
}
|
||||
|
||||
# a queued line as it will be sent, player mark and all
|
||||
export function telemetry_line(telemetry_st: TelemetryState, i: int) -> string { return telemetry_st.telemetry__q[i] }
|
||||
export function telemetry_line(i: int) -> string { return telemetry__q[i] }
|
||||
|
||||
# back to before telemetry_start, ports and config kept (tests; a start after a failed boot)
|
||||
export function telemetry_reset(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
|
||||
telemetry__drop_request(telemetry_st)
|
||||
telemetry_st.telemetry__ready = false
|
||||
telemetry_st.telemetry__id = ""
|
||||
telemetry_st.telemetry__session = ""
|
||||
telemetry_st.telemetry__q = null
|
||||
telemetry_st.telemetry__dirty = false
|
||||
telemetry_st.telemetry__sent_n = 0
|
||||
telemetry_st.telemetry__next_ms = 0
|
||||
telemetry_st.telemetry__saved_ms = 0
|
||||
telemetry_st.telemetry__fails = 0
|
||||
telemetry_st.telemetry__clock_on = false
|
||||
telemetry_st.telemetry__proc = -1
|
||||
q_clear(base_st, telemetry_facts(base_st, telemetry_st))
|
||||
export function telemetry_reset() -> void {
|
||||
telemetry__drop_request()
|
||||
telemetry__ready = false
|
||||
telemetry__id = ""
|
||||
telemetry__session = ""
|
||||
telemetry__q = null
|
||||
telemetry__dirty = false
|
||||
telemetry__sent_n = 0
|
||||
telemetry__next_ms = 0
|
||||
telemetry__saved_ms = 0
|
||||
telemetry__fails = 0
|
||||
telemetry__clock_on = false
|
||||
telemetry__proc = -1
|
||||
q_clear(telemetry_facts())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +1,90 @@
|
|||
# queue.ludic - a start, an event encoded once into a line, the queue on disk, and forgetting
|
||||
# once, at boot: who this is (from the id file, or begun from the machine), a new session, and
|
||||
# whatever an earlier start left unsent - or nothing, if the player has it off
|
||||
export function telemetry_start(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
|
||||
if telemetry_st.telemetry__ready { return }
|
||||
telemetry_st.telemetry__ready = true
|
||||
telemetry_st.telemetry__t0 = Time.now()
|
||||
telemetry_st.telemetry__session = telemetry_random_hex(telemetry_st, 16)
|
||||
telemetry_st.telemetry__q = new []string
|
||||
telemetry_st.telemetry__id = sv_str(telemetry__read_obj(telemetry__conf(telemetry_st).id_file), "id", "")
|
||||
if len(telemetry_st.telemetry__id) == 0 { telemetry__id_begin(base_st, telemetry_st) }
|
||||
if telemetry__enabled() { telemetry__queue_load(telemetry_st) } else { telemetry_forget(telemetry_st) }
|
||||
export function telemetry_start() -> void {
|
||||
if telemetry__ready { return }
|
||||
telemetry__ready = true
|
||||
telemetry__t0 = Time.now()
|
||||
telemetry__session = telemetry_random_hex(16)
|
||||
telemetry__q = new []string
|
||||
telemetry__id = sv_str(telemetry__read_obj(telemetry__conf().id_file), "id", "")
|
||||
if len(telemetry__id) == 0 { telemetry__id_begin() }
|
||||
if telemetry__enabled() { telemetry__queue_load() } else { telemetry_forget() }
|
||||
}
|
||||
|
||||
# may an event be kept right now?
|
||||
export function telemetry_on(telemetry_st: mut TelemetryState) -> bool { return telemetry_st.telemetry__ready and telemetry__enabled() and telemetry__can_send(telemetry_st) }
|
||||
export function telemetry_on() -> bool { return telemetry__ready and telemetry__enabled() and telemetry__can_send() }
|
||||
|
||||
# an event: the properties (null for none) with the session and the library added, and the player
|
||||
# as a mark put in when the batch goes, so an event queued before the id is known is not misfiled
|
||||
export function telemetry_event(telemetry_st: mut TelemetryState, name: string, props: Val) -> void {
|
||||
if not telemetry_on(telemetry_st) { return }
|
||||
export function telemetry_event(name: string, props: Val) -> void {
|
||||
if not telemetry_on() { return }
|
||||
var p = props
|
||||
if p == null { p = Value.object() }
|
||||
Value.put(p, "$session_id", Value.str(telemetry_st.telemetry__session))
|
||||
if len(telemetry__conf(telemetry_st).lib) > 0 { Value.put(p, "$lib", Value.str(telemetry__conf(telemetry_st).lib)) }
|
||||
Value.put(p, "$session_id", Value.str(telemetry__session))
|
||||
if len(telemetry__conf().lib) > 0 { Value.put(p, "$lib", Value.str(telemetry__conf().lib)) }
|
||||
let e = Value.object()
|
||||
Value.put(e, "event", Value.str(name))
|
||||
Value.put(e, "distinct_id", Value.str(TELEMETRY_MARK))
|
||||
Value.put(e, "properties", p)
|
||||
Value.put(e, "timestamp", Value.str(telemetry__stamp()))
|
||||
push(telemetry_st.telemetry__q, Json.encode(e))
|
||||
telemetry_st.telemetry__dirty = true
|
||||
let c = telemetry__conf(telemetry_st)
|
||||
if len(telemetry_st.telemetry__q) > c.queue_max + c.batch { telemetry__queue_drop(telemetry_st, len(telemetry_st.telemetry__q) - c.queue_max) }
|
||||
push(telemetry__q, Json.encode(e))
|
||||
telemetry__dirty = true
|
||||
let c = telemetry__conf()
|
||||
if len(telemetry__q) > c.queue_max + c.batch { telemetry__queue_drop(len(telemetry__q) - c.queue_max) }
|
||||
}
|
||||
|
||||
# the queue on disk now, if it changed (a quit, a crash about to happen, a child starting)
|
||||
export function telemetry_save(telemetry_st: mut TelemetryState) -> void {
|
||||
if not telemetry_st.telemetry__dirty or telemetry_st.telemetry__q == null { return }
|
||||
telemetry_st.telemetry__dirty = false
|
||||
let f = telemetry__conf(telemetry_st).queue_file
|
||||
export function telemetry_save() -> void {
|
||||
if not telemetry__dirty or telemetry__q == null { return }
|
||||
telemetry__dirty = false
|
||||
let f = telemetry__conf().queue_file
|
||||
if len(f) == 0 { return }
|
||||
if len(telemetry_st.telemetry__q) == 0 {
|
||||
if len(telemetry__q) == 0 {
|
||||
if Fs.exists(f) { Fs.remove(f) }
|
||||
return
|
||||
}
|
||||
Fs.write_text(f, Text.join(telemetry_st.telemetry__q, "\n") + "\n")
|
||||
Fs.write_text(f, Text.join(telemetry__q, "\n") + "\n")
|
||||
}
|
||||
|
||||
# the player turned it off: nothing more is sent, and nothing queued is kept, here or on disk
|
||||
export function telemetry_forget(telemetry_st: mut TelemetryState) -> void {
|
||||
telemetry__drop_request(telemetry_st)
|
||||
telemetry_st.telemetry__q = new []string
|
||||
telemetry_st.telemetry__dirty = false
|
||||
let f = telemetry__conf(telemetry_st).queue_file
|
||||
export function telemetry_forget() -> void {
|
||||
telemetry__drop_request()
|
||||
telemetry__q = new []string
|
||||
telemetry__dirty = false
|
||||
let f = telemetry__conf().queue_file
|
||||
if len(f) > 0 and Fs.exists(f) { Fs.remove(f) }
|
||||
}
|
||||
|
||||
function telemetry__queue_load(telemetry_st: mut TelemetryState) -> void {
|
||||
telemetry_st.telemetry__q = new []string
|
||||
let f = telemetry__conf(telemetry_st).queue_file
|
||||
function telemetry__queue_load() -> void {
|
||||
telemetry__q = new []string
|
||||
let f = telemetry__conf().queue_file
|
||||
if len(f) == 0 { return }
|
||||
let s = Fs.read_text(f)
|
||||
if s == null { return }
|
||||
let parts = Text.split(s, "\n")
|
||||
for i in 0 .. len(parts) { if len(parts[i]) > 2 { push(telemetry_st.telemetry__q, parts[i]) } }
|
||||
let max = telemetry__conf(telemetry_st).queue_max
|
||||
if len(telemetry_st.telemetry__q) > max { telemetry__queue_drop(telemetry_st, len(telemetry_st.telemetry__q) - max) }
|
||||
for i in 0 .. len(parts) { if len(parts[i]) > 2 { push(telemetry__q, parts[i]) } }
|
||||
let max = telemetry__conf().queue_max
|
||||
if len(telemetry__q) > max { telemetry__queue_drop(len(telemetry__q) - max) }
|
||||
}
|
||||
|
||||
# drop the first n lines (sent, or the oldest past the cap)
|
||||
function telemetry__queue_drop(telemetry_st: mut TelemetryState, n: int) -> void {
|
||||
function telemetry__queue_drop(n: int) -> void {
|
||||
let rest = new []string
|
||||
for i in n .. len(telemetry_st.telemetry__q) { push(rest, telemetry_st.telemetry__q[i]) }
|
||||
telemetry_st.telemetry__q = rest
|
||||
telemetry_st.telemetry__dirty = true
|
||||
for i in n .. len(telemetry__q) { push(rest, telemetry__q[i]) }
|
||||
telemetry__q = rest
|
||||
telemetry__dirty = true
|
||||
}
|
||||
|
||||
# the batch: the first n lines joined, with the player id put in - no parsing
|
||||
export function telemetry_batch_body(telemetry_st: mut TelemetryState, n: int) -> string {
|
||||
export function telemetry_batch_body(n: int) -> string {
|
||||
let part = new []string
|
||||
for i in 0 .. n { push(part, telemetry_st.telemetry__q[i]) }
|
||||
for i in 0 .. n { push(part, telemetry__q[i]) }
|
||||
let head = Value.object()
|
||||
Value.put(head, "api_key", Value.str(telemetry__conf(telemetry_st).key))
|
||||
Value.put(head, "api_key", Value.str(telemetry__conf().key))
|
||||
let h: string = Json.encode(head)
|
||||
let body = h[0..len(h) - 1] + ",\"batch\":[" + Text.join(part, ",") + "]}"
|
||||
return Text.replace(body, TELEMETRY_MARK, telemetry_st.telemetry__id)
|
||||
return Text.replace(body, TELEMETRY_MARK, telemetry__id)
|
||||
}
|
||||
|
||||
function telemetry__read_obj(path: string) -> Val {
|
||||
|
|
|
|||
|
|
@ -1,69 +1,70 @@
|
|||
# send.ludic - every frame: a batch when one is due, its answer when it lands, the wait doubled
|
||||
# when it failed, the queue on disk every few seconds. One request at a time; nothing waits on it.
|
||||
var telemetry__clock_on: bool = false
|
||||
|
||||
export function telemetry_tick(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
|
||||
if not telemetry_st.telemetry__ready { return }
|
||||
telemetry__id_tick(base_st, telemetry_st)
|
||||
export function telemetry_tick() -> void {
|
||||
if not telemetry__ready { return }
|
||||
telemetry__id_tick()
|
||||
if not telemetry__enabled() {
|
||||
if len(telemetry_st.telemetry__q) > 0 or telemetry_st.telemetry__h >= 0 { telemetry_forget(telemetry_st) }
|
||||
if len(telemetry__q) > 0 or telemetry__h >= 0 { telemetry_forget() }
|
||||
return
|
||||
}
|
||||
if not telemetry__can_send(telemetry_st) { return }
|
||||
if not telemetry__can_send() { return }
|
||||
let now = telemetry__now()
|
||||
let c = telemetry__conf(telemetry_st)
|
||||
if not telemetry_st.telemetry__clock_on {
|
||||
telemetry_st.telemetry__clock_on = true
|
||||
telemetry_st.telemetry__next_ms = now + c.flush_ms
|
||||
telemetry_st.telemetry__saved_ms = now
|
||||
let c = telemetry__conf()
|
||||
if not telemetry__clock_on {
|
||||
telemetry__clock_on = true
|
||||
telemetry__next_ms = now + c.flush_ms
|
||||
telemetry__saved_ms = now
|
||||
}
|
||||
if now - telemetry_st.telemetry__saved_ms >= c.save_ms {
|
||||
telemetry_save(telemetry_st)
|
||||
telemetry_st.telemetry__saved_ms = now
|
||||
if now - telemetry__saved_ms >= c.save_ms {
|
||||
telemetry_save()
|
||||
telemetry__saved_ms = now
|
||||
}
|
||||
if telemetry_st.telemetry__h >= 0 {
|
||||
telemetry__answer(base_st, telemetry_st, now)
|
||||
if telemetry__h >= 0 {
|
||||
telemetry__answer(now)
|
||||
return
|
||||
}
|
||||
if len(telemetry_st.telemetry__q) == 0 { return }
|
||||
let early = telemetry_st.telemetry__fails == 0 and len(telemetry_st.telemetry__q) >= c.flush_at
|
||||
if early or now >= telemetry_st.telemetry__next_ms {
|
||||
telemetry_save(telemetry_st)
|
||||
telemetry__flush(base_st, telemetry_st, now)
|
||||
if telemetry_st.telemetry__fails == 0 { telemetry_st.telemetry__next_ms = now + c.flush_ms }
|
||||
if len(telemetry__q) == 0 { return }
|
||||
let early = telemetry__fails == 0 and len(telemetry__q) >= c.flush_at
|
||||
if early or now >= telemetry__next_ms {
|
||||
telemetry_save()
|
||||
telemetry__flush(now)
|
||||
if telemetry__fails == 0 { telemetry__next_ms = now + c.flush_ms }
|
||||
}
|
||||
}
|
||||
|
||||
function telemetry__answer(base_st: mut BaseState, telemetry_st: mut TelemetryState, now: int) -> void {
|
||||
let st = telemetry__poll(telemetry_st.telemetry__h)
|
||||
function telemetry__answer(now: int) -> void {
|
||||
let st = telemetry__poll(telemetry__h)
|
||||
if st < 0 { return }
|
||||
telemetry_st.telemetry__h = -1
|
||||
telemetry__h = -1
|
||||
if st > 0 {
|
||||
telemetry__queue_drop(telemetry_st, telemetry_st.telemetry__sent_n)
|
||||
telemetry_st.telemetry__fails = 0
|
||||
telemetry_st.telemetry__next_ms = now + telemetry__conf(telemetry_st).flush_ms
|
||||
telemetry__fact(base_st, telemetry_st, TELEMETRY_SENT, telemetry_st.telemetry__sent_n, st, 0)
|
||||
telemetry__queue_drop(telemetry__sent_n)
|
||||
telemetry__fails = 0
|
||||
telemetry__next_ms = now + telemetry__conf().flush_ms
|
||||
telemetry__fact(TELEMETRY_SENT, telemetry__sent_n, st, 0)
|
||||
} else {
|
||||
telemetry__backoff(telemetry_st, now)
|
||||
telemetry__fact(base_st, telemetry_st, TELEMETRY_FAILED, telemetry_st.telemetry__sent_n, st, telemetry_st.telemetry__next_ms - now)
|
||||
telemetry__backoff(now)
|
||||
telemetry__fact(TELEMETRY_FAILED, telemetry__sent_n, st, telemetry__next_ms - now)
|
||||
}
|
||||
telemetry_save(telemetry_st)
|
||||
telemetry_save()
|
||||
}
|
||||
|
||||
function telemetry__flush(base_st: mut BaseState, telemetry_st: mut TelemetryState, now: int) -> void {
|
||||
if telemetry_st.telemetry__h >= 0 or len(telemetry_st.telemetry__id) == 0 or len(telemetry_st.telemetry__q) == 0 { return }
|
||||
let n = min(telemetry__conf(telemetry_st).batch, len(telemetry_st.telemetry__q))
|
||||
let h = telemetry__send(telemetry_st, telemetry_batch_body(telemetry_st, n))
|
||||
function telemetry__flush(now: int) -> void {
|
||||
if telemetry__h >= 0 or len(telemetry__id) == 0 or len(telemetry__q) == 0 { return }
|
||||
let n = min(telemetry__conf().batch, len(telemetry__q))
|
||||
let h = telemetry__send(telemetry_batch_body(n))
|
||||
if h < 0 {
|
||||
telemetry__backoff(telemetry_st, now)
|
||||
telemetry__fact(base_st, telemetry_st, TELEMETRY_FAILED, n, 0, telemetry_st.telemetry__next_ms - now)
|
||||
telemetry__backoff(now)
|
||||
telemetry__fact(TELEMETRY_FAILED, n, 0, telemetry__next_ms - now)
|
||||
return
|
||||
}
|
||||
telemetry_st.telemetry__h = h
|
||||
telemetry_st.telemetry__sent_n = n
|
||||
telemetry__h = h
|
||||
telemetry__sent_n = n
|
||||
}
|
||||
|
||||
# a failed send: wait twice as long next time, up to backoff_max doublings
|
||||
function telemetry__backoff(telemetry_st: mut TelemetryState, now: int) -> void {
|
||||
if telemetry_st.telemetry__fails < telemetry__conf(telemetry_st).backoff_max { telemetry_st.telemetry__fails += 1 }
|
||||
telemetry_st.telemetry__next_ms = now + (telemetry__conf(telemetry_st).flush_ms << telemetry_st.telemetry__fails)
|
||||
function telemetry__backoff(now: int) -> void {
|
||||
if telemetry__fails < telemetry__conf().backoff_max { telemetry__fails += 1 }
|
||||
telemetry__next_ms = now + (telemetry__conf().flush_ms << telemetry__fails)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,37 +46,30 @@ export property TelemetryFact {
|
|||
|
||||
const TELEMETRY_MARK: string = "@@TELEMETRY_PLAYER@@"
|
||||
|
||||
export state TelemetryState {
|
||||
telemetry__proc: int = -1
|
||||
telemetry__proc_out: string = ""
|
||||
telemetry__proc_t0: int = 0
|
||||
telemetry__rand_n: int = 0
|
||||
telemetry__clock_on: bool = false
|
||||
telemetry__cfg: TelemetryConfig = null
|
||||
telemetry__ready: bool = false
|
||||
telemetry__id: string = ""
|
||||
telemetry__session: string = ""
|
||||
telemetry__q: []string = null # the queued lines, oldest first
|
||||
telemetry__dirty: bool = false # telemetry__q differs from the file
|
||||
telemetry__h: int = -1 # the batch in flight
|
||||
telemetry__sent_n: int = 0 # how many lines it carries
|
||||
telemetry__next_ms: int = 0 # when the next send may go
|
||||
telemetry__saved_ms: int = 0 # when the queue was last put on disk
|
||||
telemetry__fails: int = 0 # sends failed in a row
|
||||
telemetry__t0: int = 0 # the default clock's zero, wall seconds
|
||||
telemetry__facts: Queue<TelemetryFact> = null
|
||||
var telemetry__cfg: TelemetryConfig = null
|
||||
var telemetry__ready: bool = false
|
||||
var telemetry__id: string = ""
|
||||
var telemetry__session: string = ""
|
||||
var telemetry__q: []string = null # the queued lines, oldest first
|
||||
var telemetry__dirty: bool = false # telemetry__q differs from the file
|
||||
var telemetry__h: int = -1 # the batch in flight
|
||||
var telemetry__sent_n: int = 0 # how many lines it carries
|
||||
var telemetry__next_ms: int = 0 # when the next send may go
|
||||
var telemetry__saved_ms: int = 0 # when the queue was last put on disk
|
||||
var telemetry__fails: int = 0 # sends failed in a row
|
||||
var telemetry__t0: int = 0 # the default clock's zero, wall seconds
|
||||
var telemetry__facts: Queue<TelemetryFact> = null
|
||||
|
||||
export function telemetry_facts() -> Queue<TelemetryFact> {
|
||||
if telemetry__facts == null { telemetry__facts = queue_new("telemetry.facts") }
|
||||
return telemetry__facts
|
||||
}
|
||||
|
||||
export function telemetry_facts(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> Queue<TelemetryFact> {
|
||||
if telemetry_st.telemetry__facts == null { telemetry_st.telemetry__facts = queue_new(base_st, "telemetry.facts") }
|
||||
return telemetry_st.telemetry__facts
|
||||
}
|
||||
|
||||
function telemetry__fact(base_st: mut BaseState, telemetry_st: mut TelemetryState, what: int, count: int, status: int, retry: int) -> void {
|
||||
function telemetry__fact(what: int, count: int, status: int, retry: int) -> void {
|
||||
let f = new TelemetryFact
|
||||
f.what = what
|
||||
f.count = count
|
||||
f.status = status
|
||||
f.retry_ms = retry
|
||||
q_push(base_st, telemetry_facts(base_st, telemetry_st), f)
|
||||
q_push(telemetry_facts(), f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,29 +4,27 @@ import "ludic.telemetry"
|
|||
import "ludic.base"
|
||||
program TelemetryTest {
|
||||
numbers float
|
||||
state TelemetrytestState {
|
||||
clock: int = 0
|
||||
on: bool = true
|
||||
allowed: bool = true
|
||||
answer: int = -1
|
||||
refuse: bool = false
|
||||
sends: int = 0
|
||||
last_url: string = ""
|
||||
last_body: string = ""
|
||||
}
|
||||
var clock: int = 0
|
||||
var on: bool = true
|
||||
var allowed: bool = true
|
||||
var answer: int = -1
|
||||
var refuse: bool = false
|
||||
var sends: int = 0
|
||||
var last_url: string = ""
|
||||
var last_body: string = ""
|
||||
|
||||
function fake_now(telemetrytest_st: TelemetrytestState) -> int { return telemetrytest_st.clock }
|
||||
function fake_enabled(telemetrytest_st: TelemetrytestState) -> bool { return telemetrytest_st.on }
|
||||
function fake_can(telemetrytest_st: TelemetrytestState) -> bool { return telemetrytest_st.allowed }
|
||||
function fake_now() -> int { return clock }
|
||||
function fake_enabled() -> bool { return on }
|
||||
function fake_can() -> bool { return allowed }
|
||||
function fake_stamp() -> string { return "2026-09-25T12:00:00Z" }
|
||||
function fake_send(telemetrytest_st: mut TelemetrytestState, url: string, body: string) -> int {
|
||||
if telemetrytest_st.refuse { return -1 }
|
||||
telemetrytest_st.sends += 1
|
||||
telemetrytest_st.last_url = url
|
||||
telemetrytest_st.last_body = body
|
||||
function fake_send(url: string, body: string) -> int {
|
||||
if refuse { return -1 }
|
||||
sends += 1
|
||||
last_url = url
|
||||
last_body = body
|
||||
return 7
|
||||
}
|
||||
function fake_poll(telemetrytest_st: TelemetrytestState, h: int) -> int { return telemetrytest_st.answer }
|
||||
function fake_poll(h: int) -> int { return answer }
|
||||
function fake_drop(h: int) -> void { }
|
||||
|
||||
bind TelemetryWorld { can_send: fn fake_can, enabled: fn fake_enabled, now_ms: fn fake_now, stamp: fn fake_stamp }
|
||||
|
|
@ -35,16 +33,16 @@ program TelemetryTest {
|
|||
function qfile() -> string { return Os.temp_dir() + "/ludic-telemetry-test-queue.txt" }
|
||||
function idfile() -> string { return Os.temp_dir() + "/ludic-telemetry-test-id.json" }
|
||||
|
||||
function fresh(base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) -> void {
|
||||
telemetrytest_st.clock = 0
|
||||
telemetrytest_st.on = true
|
||||
telemetrytest_st.allowed = true
|
||||
telemetrytest_st.answer = -1
|
||||
telemetrytest_st.refuse = false
|
||||
telemetrytest_st.sends = 0
|
||||
telemetrytest_st.last_url = ""
|
||||
telemetrytest_st.last_body = ""
|
||||
telemetry_reset(base_st, telemetry_st)
|
||||
function fresh() -> void {
|
||||
clock = 0
|
||||
on = true
|
||||
allowed = true
|
||||
answer = -1
|
||||
refuse = false
|
||||
sends = 0
|
||||
last_url = ""
|
||||
last_body = ""
|
||||
telemetry_reset()
|
||||
if Fs.exists(qfile()) { Fs.remove(qfile()) }
|
||||
Fs.write_text(idfile(), "{\"id\":\"p-1\",\"notices\":2}")
|
||||
let c = new TelemetryConfig
|
||||
|
|
@ -59,144 +57,144 @@ program TelemetryTest {
|
|||
c.queue_max = 8
|
||||
c.backoff_max = 2
|
||||
c.save_ms = 100000
|
||||
telemetry_config(telemetry_st, c)
|
||||
telemetry_start(base_st, telemetry_st)
|
||||
telemetry_tick(base_st, telemetry_st)
|
||||
telemetry_config(c)
|
||||
telemetry_start()
|
||||
telemetry_tick()
|
||||
}
|
||||
|
||||
function events(telemetry_st: mut TelemetryState, n: int) -> void {
|
||||
for i in 0 .. n { telemetry_event(telemetry_st, `e{i}`, null) }
|
||||
function events(n: int) -> void {
|
||||
for i in 0 .. n { telemetry_event(`e{i}`, null) }
|
||||
}
|
||||
|
||||
function at(base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState, ms: int) -> void {
|
||||
telemetrytest_st.clock = ms
|
||||
telemetry_tick(base_st, telemetry_st)
|
||||
function at(ms: int) -> void {
|
||||
clock = ms
|
||||
telemetry_tick()
|
||||
}
|
||||
|
||||
function facts_of(base_st: mut BaseState, telemetry_st: mut TelemetryState, what: int) -> int {
|
||||
let fs = q_drain(base_st, telemetry_facts(base_st, telemetry_st))
|
||||
function facts_of(what: int) -> int {
|
||||
let fs = q_drain(telemetry_facts())
|
||||
var n = 0
|
||||
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
|
||||
return n
|
||||
}
|
||||
|
||||
test "an event is one line with the player as a mark, and the batch puts the id in" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
expect(telemetry_id(telemetry_st) == "p-1")
|
||||
telemetry_event(telemetry_st, "hello", null)
|
||||
expect_eq(telemetry_queued(telemetry_st), 1)
|
||||
let line = telemetry_line(telemetry_st, 0)
|
||||
test "an event is one line with the player as a mark, and the batch puts the id in" {
|
||||
fresh()
|
||||
expect(telemetry_id() == "p-1")
|
||||
telemetry_event("hello", null)
|
||||
expect_eq(telemetry_queued(), 1)
|
||||
let line = telemetry_line(0)
|
||||
expect(Text.contains(line, "\"event\":\"hello\""))
|
||||
expect(Text.contains(line, "test-game"))
|
||||
expect(not Text.contains(line, "p-1"))
|
||||
let body = telemetry_batch_body(telemetry_st, 1)
|
||||
let body = telemetry_batch_body(1)
|
||||
expect(Text.contains(body, "\"api_key\":\"k-1\""))
|
||||
expect(Text.contains(body, "\"distinct_id\":\"p-1\""))
|
||||
}
|
||||
|
||||
test "a batch goes when due, and only what the server took leaves the queue" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
events(telemetry_st, 4)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 500)
|
||||
expect_eq(telemetrytest_st.sends, 0)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 1000)
|
||||
expect_eq(telemetrytest_st.sends, 1)
|
||||
expect(telemetrytest_st.last_url == "http://fake/batch/")
|
||||
expect(telemetry_in_flight(telemetry_st))
|
||||
events(telemetry_st, 1)
|
||||
telemetrytest_st.answer = 1
|
||||
at(base_st, telemetry_st, telemetrytest_st, 1100)
|
||||
expect_eq(telemetry_queued(telemetry_st), 2)
|
||||
expect_eq(facts_of(base_st, telemetry_st, TELEMETRY_SENT), 1)
|
||||
test "a batch goes when due, and only what the server took leaves the queue" {
|
||||
fresh()
|
||||
events(4)
|
||||
at(500)
|
||||
expect_eq(sends, 0)
|
||||
at(1000)
|
||||
expect_eq(sends, 1)
|
||||
expect(last_url == "http://fake/batch/")
|
||||
expect(telemetry_in_flight())
|
||||
events(1)
|
||||
answer = 1
|
||||
at(1100)
|
||||
expect_eq(telemetry_queued(), 2)
|
||||
expect_eq(facts_of(TELEMETRY_SENT), 1)
|
||||
}
|
||||
|
||||
test "enough events go early" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
events(telemetry_st, 5)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 10)
|
||||
expect_eq(telemetrytest_st.sends, 1)
|
||||
test "enough events go early" {
|
||||
fresh()
|
||||
events(5)
|
||||
at(10)
|
||||
expect_eq(sends, 1)
|
||||
}
|
||||
|
||||
test "a failure keeps the lines and waits twice as long, up to the cap" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
events(telemetry_st, 2)
|
||||
telemetrytest_st.answer = 0
|
||||
at(base_st, telemetry_st, telemetrytest_st, 1000)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 1001)
|
||||
expect_eq(telemetry_queued(telemetry_st), 2)
|
||||
expect_eq(telemetry_fails(telemetry_st), 1)
|
||||
expect_eq(telemetry_next_ms(telemetry_st), 1001 + 2000)
|
||||
expect_eq(facts_of(base_st, telemetry_st, TELEMETRY_FAILED), 1)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 3001)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 3002)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 7002)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 7003)
|
||||
expect_eq(telemetry_fails(telemetry_st), 2)
|
||||
expect_eq(telemetry_next_ms(telemetry_st), 7003 + 4000)
|
||||
test "a failure keeps the lines and waits twice as long, up to the cap" {
|
||||
fresh()
|
||||
events(2)
|
||||
answer = 0
|
||||
at(1000)
|
||||
at(1001)
|
||||
expect_eq(telemetry_queued(), 2)
|
||||
expect_eq(telemetry_fails(), 1)
|
||||
expect_eq(telemetry_next_ms(), 1001 + 2000)
|
||||
expect_eq(facts_of(TELEMETRY_FAILED), 1)
|
||||
at(3001)
|
||||
at(3002)
|
||||
at(7002)
|
||||
at(7003)
|
||||
expect_eq(telemetry_fails(), 2)
|
||||
expect_eq(telemetry_next_ms(), 7003 + 4000)
|
||||
}
|
||||
|
||||
test "a request that cannot start backs off too" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
telemetrytest_st.refuse = true
|
||||
events(telemetry_st, 1)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 1000)
|
||||
expect_eq(telemetry_fails(telemetry_st), 1)
|
||||
expect(not telemetry_in_flight(telemetry_st))
|
||||
test "a request that cannot start backs off too" {
|
||||
fresh()
|
||||
refuse = true
|
||||
events(1)
|
||||
at(1000)
|
||||
expect_eq(telemetry_fails(), 1)
|
||||
expect(not telemetry_in_flight())
|
||||
}
|
||||
|
||||
test "off keeps nothing and deletes the file" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
events(telemetry_st, 3)
|
||||
telemetry_save(telemetry_st)
|
||||
test "off keeps nothing and deletes the file" {
|
||||
fresh()
|
||||
events(3)
|
||||
telemetry_save()
|
||||
expect(Fs.exists(qfile()))
|
||||
telemetrytest_st.on = false
|
||||
at(base_st, telemetry_st, telemetrytest_st, 10)
|
||||
expect_eq(telemetry_queued(telemetry_st), 0)
|
||||
on = false
|
||||
at(10)
|
||||
expect_eq(telemetry_queued(), 0)
|
||||
expect(not Fs.exists(qfile()))
|
||||
events(telemetry_st, 3)
|
||||
expect_eq(telemetry_queued(telemetry_st), 0)
|
||||
events(3)
|
||||
expect_eq(telemetry_queued(), 0)
|
||||
}
|
||||
|
||||
test "a run that may not send queues nothing and sends nothing" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
telemetrytest_st.allowed = false
|
||||
events(telemetry_st, 9)
|
||||
at(base_st, telemetry_st, telemetrytest_st, 100000)
|
||||
expect_eq(telemetry_queued(telemetry_st), 0)
|
||||
expect_eq(telemetrytest_st.sends, 0)
|
||||
test "a run that may not send queues nothing and sends nothing" {
|
||||
fresh()
|
||||
allowed = false
|
||||
events(9)
|
||||
at(100000)
|
||||
expect_eq(telemetry_queued(), 0)
|
||||
expect_eq(sends, 0)
|
||||
}
|
||||
|
||||
test "the queue is capped at its newest events" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
telemetrytest_st.allowed = true
|
||||
events(telemetry_st, 12)
|
||||
expect(telemetry_queued(telemetry_st) <= 11)
|
||||
expect(Text.contains(telemetry_line(telemetry_st, telemetry_queued(telemetry_st) - 1), "e11"))
|
||||
test "the queue is capped at its newest events" {
|
||||
fresh()
|
||||
allowed = true
|
||||
events(12)
|
||||
expect(telemetry_queued() <= 11)
|
||||
expect(Text.contains(telemetry_line(telemetry_queued() - 1), "e11"))
|
||||
}
|
||||
|
||||
test "what was not sent is there at the next start" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
events(telemetry_st, 3)
|
||||
telemetry_save(telemetry_st)
|
||||
telemetry_reset(base_st, telemetry_st)
|
||||
telemetry_start(base_st, telemetry_st)
|
||||
expect_eq(telemetry_queued(telemetry_st), 3)
|
||||
test "what was not sent is there at the next start" {
|
||||
fresh()
|
||||
events(3)
|
||||
telemetry_save()
|
||||
telemetry_reset()
|
||||
telemetry_start()
|
||||
expect_eq(telemetry_queued(), 3)
|
||||
}
|
||||
|
||||
test "an id set outright is written beside the file's other keys" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
telemetry_set_id(base_st, telemetry_st, "p-2")
|
||||
test "an id set outright is written beside the file's other keys" {
|
||||
fresh()
|
||||
telemetry_set_id("p-2")
|
||||
let v = Json.parse(Fs.read_text(idfile()))
|
||||
expect(sv_str(v, "id", "") == "p-2")
|
||||
expect_eq(sv_int(v, "notices", 0), 2)
|
||||
}
|
||||
|
||||
test "no salt and no id file makes a random id" (base_st: mut BaseState, telemetry_st: mut TelemetryState, telemetrytest_st: mut TelemetrytestState) {
|
||||
fresh(base_st, telemetry_st, telemetrytest_st)
|
||||
telemetry_reset(base_st, telemetry_st)
|
||||
test "no salt and no id file makes a random id" {
|
||||
fresh()
|
||||
telemetry_reset()
|
||||
Fs.remove(idfile())
|
||||
telemetry_start(base_st, telemetry_st)
|
||||
expect(Text.starts_with(telemetry_id(telemetry_st), "r-"))
|
||||
expect_eq(len(telemetry_session(telemetry_st)), 32)
|
||||
telemetry_start()
|
||||
expect(Text.starts_with(telemetry_id(), "r-"))
|
||||
expect_eq(len(telemetry_session()), 32)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue