diff --git a/packages/ludic.effects/queries.ludic b/packages/ludic.effects/queries.ludic index 65245069..0ec4dc0f 100644 --- a/packages/ludic.effects/queries.ludic +++ b/packages/ludic.effects/queries.ludic @@ -48,6 +48,7 @@ export function effects_live(effects_st: EffectsState) -> []int { export function effects_live_into(effects_st: EffectsState, out: []int) -> void { List.clear(out) let all = ef_all(effects_st) + @alloc_ok("into the caller's kept list, at most the ring's size") for i in 0 .. len(all) { if ef_live(all[i]) { push(out, i) } } } diff --git a/packages/ludic.effects/verbs.ludic b/packages/ludic.effects/verbs.ludic index 8a7e6c45..bf9bff6f 100644 --- a/packages/ludic.effects/verbs.ludic +++ b/packages/ludic.effects/verbs.ludic @@ -1,5 +1,6 @@ # verbs.ludic - the only way the ring changes. The same kind from the same label replaces # itself; a bonus past the cap pushes out the oldest bonus; a full ring drops the nearest end. +@alloc_ok("an effect's end: a fact per effect that runs out or is replaced") function ef_end(effects_st: mut EffectsState, i: int, why: int) -> void { let all = ef_all(effects_st) let e = new EffectEnded @@ -11,6 +12,7 @@ function ef_end(effects_st: mut EffectsState, i: int, why: int) -> void { ef_drop(effects_st, i) } +@alloc_ok("an effect gone: the ring rebuilt, a few times a game hour") function ef_drop(effects_st: mut EffectsState, i: int) -> void { let all = ef_all(effects_st) let rest = new []Effect @@ -37,6 +39,7 @@ function ef_make_room(effects_st: mut EffectsState, bonus: bool) -> void { } # begin one `after` game minutes from now, lasting `minutes` once it has begun +@alloc_ok("an effect begun: food, rest, the weather - an event, not a frame") export function effects_add_after(effects_st: mut EffectsState, after: float, kind: int, mag: float, minutes: float, bonus: bool, label: string) -> void { let all = ef_all(effects_st) for i in 0 .. len(all) { @@ -62,6 +65,7 @@ export function effects_add(effects_st: mut EffectsState, kind: int, mag: float, } # everything stops at once, and says nothing: a new trip or a night's sleep +@alloc_ok("a new trip or a night's sleep") export function effects_clear(effects_st: mut EffectsState) -> void { effects_st.ef_list = new []Effect } # run them down by `minutes` of game time; a delayed one waits first diff --git a/packages/ludic.steps/arc.ludic b/packages/ludic.steps/arc.ludic index a5789e2f..90bec221 100644 --- a/packages/ludic.steps/arc.ludic +++ b/packages/ludic.steps/arc.ludic @@ -1,6 +1,7 @@ # arc.ludic - an arc is a table of chapters, each a table of up to STEPS_PER_CHAPTER steps. The # game builds one with steps_chapter_add / steps_add; what a chapter is called is the game's. # empty the tables for another arc (the progress goes back to the first chapter) +@alloc_ok("an arc's tables: once, when an arc is built") export function steps_arc_clear(steps_st: mut StepsState) -> void { steps_st.steps__n = new []int steps_st.steps__kind = new []int diff --git a/packages/ludic.steps/party.ludic b/packages/ludic.steps/party.ludic index b1d3de5c..edb9e2e0 100644 --- a/packages/ludic.steps/party.ludic +++ b/packages/ludic.steps/party.ludic @@ -8,6 +8,7 @@ export function steps_config(steps_st: mut StepsState, players: int) -> void { steps_st.steps__col = null } +@alloc_ok("the party's columns: once a chapter, and when the party changes") function steps__party_clear(steps_st: mut StepsState) -> void { steps_st.steps__col = new []int for i in 0 .. steps_st.steps__players * STEPS_PER_CHAPTER { push(steps_st.steps__col, 0) } @@ -36,6 +37,7 @@ function steps__share(steps_st: StepsState, p: int, i: int) -> int { # the same into a list the caller keeps (a guest checks its shares every tick) export function steps_my_shares_into(steps_st: StepsState, out: []int) -> void { List.clear(out) + @alloc_ok("into the caller's kept list, at most STEPS_PER_CHAPTER") for i in 0 .. STEPS_PER_CHAPTER { push(out, steps__share(steps_st, 0, i)) } } diff --git a/packages/ludic.steps/state.ludic b/packages/ludic.steps/state.ludic index 2de19892..301f16e6 100644 --- a/packages/ludic.steps/state.ludic +++ b/packages/ludic.steps/state.ludic @@ -63,6 +63,7 @@ export function steps_facts(steps_st: StepsState) -> Queue { return steps_st.steps__facts } +@alloc_ok("a step's fact: once, when a step or a chapter is met") function steps__fact(steps_st: StepsState, what: int, ch: int, i: int) -> void { let f = new StepsFact f.what = what diff --git a/packages/ludic.telemetry/facts.ludic b/packages/ludic.telemetry/facts.ludic index a56dfe01..6d6634e2 100644 --- a/packages/ludic.telemetry/facts.ludic +++ b/packages/ludic.telemetry/facts.ludic @@ -10,7 +10,9 @@ function telemetry__fact_record(telemetry_st: mut TelemetryState) -> TelemetryFa telemetry_st.telemetry__fnext[0] += 1 return f } + @alloc_ok("the fact pool grows to the most facts in flight at once") let f = new TelemetryFact + @alloc_ok("the fact pool grows to the most facts in flight at once") push(telemetry_st.telemetry__fpool, f) return f } diff --git a/packages/ludic.telemetry/ident.ludic b/packages/ludic.telemetry/ident.ludic index 9d53d170..cf749dcf 100644 --- a/packages/ludic.telemetry/ident.ludic +++ b/packages/ludic.telemetry/ident.ludic @@ -3,6 +3,7 @@ # 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 +@alloc_ok("the player id: once a start, and never again once written") 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}` @@ -23,8 +24,10 @@ export function telemetry_set_id(telemetry_st: mut TelemetryState, id: string) - telemetry__fact(telemetry_st, TELEMETRY_ID, 0, 0, 0) } +@alloc_ok("the player id: once a start") function telemetry__random_id(telemetry_st: mut TelemetryState) -> void { telemetry_set_id(telemetry_st, "r-" + telemetry_random_hex(telemetry_st, 16)) } +@alloc_ok("the player id: once a start") function telemetry__from_machine(telemetry_st: mut TelemetryState, raw: string) -> void { let salt = telemetry__conf(telemetry_st).id_salt telemetry_set_id(telemetry_st, "m-" + Crypto.sha256(salt + ":" + Text.lower(raw))[0..32]) diff --git a/packages/ludic.telemetry/props_nest.ludic b/packages/ludic.telemetry/props_nest.ludic index bb539a4b..5561eeca 100644 --- a/packages/ludic.telemetry/props_nest.ludic +++ b/packages/ludic.telemetry/props_nest.ludic @@ -8,6 +8,7 @@ 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) + @alloc_ok("one nesting stack per props record, the first time it nests") if p.lv == null { p.lv = words(8) } if p.d < len(p.lv) - 1 { p.d += 1 } p.lv[p.d] = 0 diff --git a/packages/ludic.update/install.ludic b/packages/ludic.update/install.ludic index 49e85852..5048618e 100644 --- a/packages/ludic.update/install.ludic +++ b/packages/ludic.update/install.ludic @@ -76,7 +76,10 @@ export function update_translocated() -> bool { } # can this copy update itself? A copy Velopack did not install downloads the game again instead. -export function update_installed(update_st: UpdateState) -> bool { +export function update_installed(update_st: UpdateState) -> bool { return update_st.update__installed } + +@alloc_ok("once, when the updater is configured") +function update__installed_now(update_st: UpdateState) -> bool { if len(update__conf(update_st).root) > 0 { return true } if update_translocated() { return false } if update__mac() { return Fs.exists(update_dir() + "/UpdateMac") } diff --git a/packages/ludic.update/latest.ludic b/packages/ludic.update/latest.ludic index 0b3b4193..bd7b0415 100644 --- a/packages/ludic.update/latest.ludic +++ b/packages/ludic.update/latest.ludic @@ -6,6 +6,7 @@ # played are kept too (a launcher shows them) export function update_check_tick(update_st: mut UpdateState, show_current: bool) -> void { if update_st.update__ask == UPDATE_ASK_IDLE { + @alloc_ok("the feed's address: once, when the check starts") update_st.update__ask_h = UpdateNet.get(update__conf(update_st).feed + "/" + update__conf(update_st).latest) update_st.update__ask = UPDATE_ASKING if update_st.update__ask_h < 0 { update_st.update__ask = UPDATE_NONE } @@ -36,16 +37,23 @@ export function update_latest(update_st: UpdateState) -> string { return update_ export function update_shown(update_st: UpdateState) -> bool { return update_st.update__ask == UPDATE_NEWER or update_st.update__ask == UPDATE_CURRENT } export function update_fresh(update_st: UpdateState) -> bool { return update_st.update__ask == UPDATE_NEWER } -# the notes in this language, else English, else none -export function update_notes(update_st: UpdateState, lang: string) -> []string { - let out = new []string - if update_st.update__notes == null or Value.kind(update_st.update__notes) != 6 { return out } +# the notes in the language the game asked for (update_notes_for), else English, else none +export function update_notes(update_st: UpdateState, lang: string) -> []string { return update_st.update__notes_list } + +# the notes kept for this language: filled when the version or the language changes, so the panel +# reading them every frame makes nothing +export function update_notes_for(update_st: mut UpdateState, lang: string) -> void { + if (update_st.update__notes_lang == lang) and (update_st.update__notes_of == update_st.update__latest) { return } + update_st.update__notes_lang = lang + update_st.update__notes_of = update_st.update__latest + let out = update_st.update__notes_list + List.clear(out) + if update_st.update__notes == null or Value.kind(update_st.update__notes) != 6 { return } var key = lang if Value.has(update_st.update__notes, key) == 0 { key = "en" } - if Value.has(update_st.update__notes, key) == 0 { return out } + if Value.has(update_st.update__notes, key) == 0 { return } let l = Value.get(update_st.update__notes, key) - for i in 0 .. Value.count(l) { push(out, Value.as_str(Value.at(l, i))) } - return out + for i in 0 .. Value.count(l) { push(update_st.update__notes_list, Value.as_str(Value.at(l, i))) } } # back to before anything was asked or installed (tests) diff --git a/packages/ludic.update/state.ludic b/packages/ludic.update/state.ludic index e5394d84..734be6c3 100644 --- a/packages/ludic.update/state.ludic +++ b/packages/ludic.update/state.ludic @@ -56,6 +56,10 @@ export state UpdateState { update__ask_h: int = -1 update__latest: string = "" update__notes: Val = null + update__installed: bool = false # worked out when configured: the panel asks every frame + @max(256) update__notes_list: []string = new []string # the notes in the language last asked, kept + update__notes_lang: string = "" + update__notes_of: string = "" # ... for this version update__cfg: UpdateConfig = new UpdateConfig update__state: int = 0 update__err: int = 0 @@ -80,7 +84,10 @@ export state UpdateState { update__facts: Queue = update__facts__new() } -export function update_config(update_st: mut UpdateState, c: UpdateConfig) -> void { update_st.update__cfg = c } +export function update_config(update_st: mut UpdateState, c: UpdateConfig) -> void { + update_st.update__cfg = c + update_st.update__installed = update__installed_now(update_st) +} function update__conf(update_st: UpdateState) -> UpdateConfig { return update_st.update__cfg