# state.ludic - the game's config, the ports, the facts, and what is queued # where the events go and how often. An empty host or key sends nothing, ever. export property TelemetryConfig { host: string = "" # "https://analytics.example.com" key: string = "" # the project's public token (PostHog's phc_...) path: string = "/batch/" # the batch endpoint under the host lib: string = "" # every event's $lib queue_file: string = "" # the unsent lines between starts; "" keeps them in memory only id_file: string = "" # the player id's JSON file ({"id": ...}; other keys are kept) id_salt: string = "" # hashed with the machine's own id; "" makes a random id instead id_scratch: string = "" # where the machine-id child writes (unset: the temp directory) flush_ms: int = 30000 # a batch this often flush_at: int = 50 # or sooner, at this many events batch: int = 200 # events in one request queue_max: int = 5000 # the oldest go past this, or past ring_bytes of lines ring_bytes: int = 1048576 # the lines' ring (ring.ludic), made once at the start backoff_max: int = 6 # the wait doubles at most this many times save_ms: int = 5000 # the queue on disk at most this far behind } # what telemetry asks the game (unbound: yes, on, and the wall clock) export port TelemetryWorld { can_send: fn() -> bool = fn telemetry__yes # may this run send at all (a test, a dev build: no) enabled: fn() -> bool = fn telemetry__yes # the player's switch; off forgets everything queued now_ms: fn() -> int = fn telemetry__wall_ms # a clock in milliseconds clock_s: fn() -> int = fn telemetry__wall_s # the wall clock in seconds since 1970, for an event's time } # how a batch travels (unbound: Http). send returns a handle (< 0: could not start); poll # answers -1 while pending, 0 on failure, 1 when the server took it; drop abandons one. export port TelemetryTransport { send: fn(string, []byte, int) -> int = fn telemetry__http_send # (url, body, its length) poll: fn(int) -> int = fn telemetry__http_poll drop: fn(int) -> void = fn telemetry__http_drop } export const TELEMETRY_SENT: int = 0 # a batch went (count) export const TELEMETRY_FAILED: int = 1 # a batch did not (status, retry_ms) export const TELEMETRY_ID: int = 2 # the player id became known export property TelemetryFact { what: int = 0 count: int = 0 status: int = 0 retry_ms: int = 0 } 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 = new TelemetryConfig telemetry__ready: bool = false telemetry__id: string = "" telemetry__session: string = "" telemetry__ring: []byte = null # the queued lines (ring.ludic), oldest first from head telemetry__off: []int = null telemetry__len: []int = null telemetry__mk: []int = null # where each line's player mark is, -1 none telemetry__head: int = 0 telemetry__n: int = 0 telemetry__wr: int = 0 telemetry__line: StrBuf = sb_new(TELEMETRY_LINE) # an event's line, written here telemetry__props: TelemetryProps = telemetry__props_new() telemetry__out: StrBuf = null # a batch or the file, written here telemetry__url: string = "" # host and path, joined once at the config @max(1024) telemetry__fpool: []TelemetryFact = new []TelemetryFact # fact records, reused once no drain holds one @max(1024) telemetry__ffree: []TelemetryFact = new []TelemetryFact telemetry__fnext: []int = words(1) telemetry__dirty: bool = false # the ring 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 = telemetry__facts__new() } export function telemetry_facts(telemetry_st: TelemetryState) -> Queue { return telemetry_st.telemetry__facts } function telemetry__fact(telemetry_st: mut TelemetryState, what: int, count: int, status: int, retry: int) -> void { let f = telemetry__fact_record(telemetry_st) f.what = what f.count = count f.status = status f.retry_ms = retry q_push(telemetry_facts(telemetry_st), f) } function telemetry__facts__new() -> Queue { return queue_new("telemetry.facts") } function telemetry__props_new() -> TelemetryProps { let p = new TelemetryProps p.sb = sb_new(TELEMETRY_LINE) p.lv = words(8) return p }