diff --git a/packages/ludic.fishing/facts.ludic b/packages/ludic.fishing/facts.ludic new file mode 100644 index 00000000..1df7836e --- /dev/null +++ b/packages/ludic.fishing/facts.ludic @@ -0,0 +1,19 @@ +# facts.ludic - a fishing fact's record: one no drain holds any more, made fresh (and kept) only when +# every one is held. A cast, a bite, every crossing and run of a fight is a fact, and a record a fact +# is never given back (the ludic.wildlife idiom, facts.ludic there) +function fishing_fact_record(fishing_st: mut FishingState) -> FishingFact { + if fishing_st.fishing_fnext[0] >= len(fishing_st.fishing_ffree) { + q_unheld(fishing_st.fishing_fact_q, fishing_st.fishing_fpool, fishing_st.fishing_ffree) + fishing_st.fishing_fnext[0] = 0 + } + if fishing_st.fishing_fnext[0] < len(fishing_st.fishing_ffree) { + let f = fishing_st.fishing_ffree[fishing_st.fishing_fnext[0]] + fishing_st.fishing_fnext[0] += 1 + return f + } + let f = new FishingFact + push(fishing_st.fishing_fpool, f) + return f +} +# how many fact records have ever been made: the peak held at once, not the facts said +export function fishing_fact_records(fishing_st: FishingState) -> int { return len(fishing_st.fishing_fpool) } diff --git a/packages/ludic.fishing/index.ludic b/packages/ludic.fishing/index.ludic index cc285a58..db737519 100644 --- a/packages/ludic.fishing/index.ludic +++ b/packages/ludic.fishing/index.ludic @@ -6,6 +6,7 @@ import "ludic.base" import "kinds.ludic" import "ports.ludic" import "state.ludic" +import "facts.ludic" import "fight.ludic" import "verbs.ludic" import "run.ludic" diff --git a/packages/ludic.fishing/state.ludic b/packages/ludic.fishing/state.ludic index b572d9c8..72babe6f 100644 --- a/packages/ludic.fishing/state.ludic +++ b/packages/ludic.fishing/state.ludic @@ -25,6 +25,9 @@ export state FishingState { fishing_run_next: float = 0.0 fishing_dice: Rng = null fishing_fact_q: Queue = fishing_fact_q__new() + fishing_fpool: []FishingFact = new []FishingFact # every fact record made, reused once no drain holds it (facts.ludic) + fishing_ffree: []FishingFact = new []FishingFact + fishing_fnext: []int = words(1) fishing_n: int = 0 # the record: caught, the biggest (cm), a legend landed fishing_big: int = 0 fishing_leg: bool = false @@ -51,8 +54,8 @@ function fishing_rand(fishing_st: mut FishingState) -> float { return rng_float(fishing_st.fishing_dice) } -function fishing_say(fishing_st: FishingState, what: int) -> void { - let f = new FishingFact +function fishing_say(fishing_st: mut FishingState, what: int) -> void { + let f = fishing_fact_record(fishing_st) f.what = what f.species = fishing_st.fishing_sp f.cm = fishing_st.fishing_len diff --git a/packages/ludic.fishing/tests/fishing_test.ludic b/packages/ludic.fishing/tests/fishing_test.ludic index 80aad898..bf510c4d 100644 --- a/packages/ludic.fishing/tests/fishing_test.ludic +++ b/packages/ludic.fishing/tests/fishing_test.ludic @@ -254,4 +254,20 @@ program FishingTest { expect(fishing_legend_caught(fishing_st)) expect(not fishing_active(fishing_st)) } + + test "fifty fish, their facts drained each time, reuse a handful of records" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) { + fresh(fishing_st, fishing_test_st) + for k in 0 .. 50 { + fishing_cast(fishing_st, 20.0, 0.0) + facts(fishing_st) + hook(fishing_st) + facts(fishing_st) + play(fishing_st, 12) + facts(fishing_st) + fishing_stop(fishing_st) + facts(fishing_st) + } + print(`fishing: 50 fish, {fishing_fact_records(fishing_st)} fact records made`) + expect(fishing_fact_records(fishing_st) < 30) + } } diff --git a/packages/ludic.jobs/facts.ludic b/packages/ludic.jobs/facts.ludic new file mode 100644 index 00000000..23f67446 --- /dev/null +++ b/packages/ludic.jobs/facts.ludic @@ -0,0 +1,19 @@ +# facts.ludic - a jobs fact's record: one no drain holds any more, made fresh (and kept) only when +# every one is held. A deed that moves a job is a fact, and a record a fact is never given back +# (the ludic.wildlife idiom) +function jobs__fact_record(jobs_st: mut JobsState) -> JobsFact { + if jobs_st.jobs__fnext[0] >= len(jobs_st.jobs__ffree) { + q_unheld(jobs_st.jobs__q, jobs_st.jobs__fpool, jobs_st.jobs__ffree) + jobs_st.jobs__fnext[0] = 0 + } + if jobs_st.jobs__fnext[0] < len(jobs_st.jobs__ffree) { + let f = jobs_st.jobs__ffree[jobs_st.jobs__fnext[0]] + jobs_st.jobs__fnext[0] += 1 + return f + } + let f = new JobsFact + push(jobs_st.jobs__fpool, f) + return f +} +# how many fact records have ever been made: the peak held at once, not the facts said +export function jobs_fact_records(jobs_st: JobsState) -> int { return len(jobs_st.jobs__fpool) } diff --git a/packages/ludic.jobs/index.ludic b/packages/ludic.jobs/index.ludic index 6ff8a86a..30203e2b 100644 --- a/packages/ludic.jobs/index.ludic +++ b/packages/ludic.jobs/index.ludic @@ -6,6 +6,7 @@ import "ludic.base" import "defs.ludic" import "ports.ludic" import "state.ludic" +import "facts.ludic" import "jobs.ludic" import "giveup.ludic" import "count.ludic" diff --git a/packages/ludic.jobs/state.ludic b/packages/ludic.jobs/state.ludic index 370701f2..509038c8 100644 --- a/packages/ludic.jobs/state.ludic +++ b/packages/ludic.jobs/state.ludic @@ -22,6 +22,9 @@ export state JobsState { jobs__seed: int = 1000 jobs__dice: Rng = rng_new(1000) jobs__q: Queue = jobs__q__new() + jobs__fpool: []JobsFact = new []JobsFact # every fact record made, reused once no drain holds it (facts.ludic) + jobs__ffree: []JobsFact = new []JobsFact + jobs__fnext: []int = words(1) } # the posts, every board's slots end to end from jobs__off[b] @@ -58,8 +61,8 @@ export function jobs_facts(jobs_st: JobsState) -> Queue { return jobs_st.jobs__q } -function jobs__fact(jobs_st: JobsState, what: int, job: int, b: int, s: int, have: int, need: int) -> void { - let f = new JobsFact +function jobs__fact(jobs_st: mut JobsState, what: int, job: int, b: int, s: int, have: int, need: int) -> void { + let f = jobs__fact_record(jobs_st) f.what = what f.job = job f.board = b diff --git a/packages/ludic.jobs/tests/giveup_test.ludic b/packages/ludic.jobs/tests/giveup_test.ludic index 96b83c66..49a600b6 100644 --- a/packages/ludic.jobs/tests/giveup_test.ludic +++ b/packages/ludic.jobs/tests/giveup_test.ludic @@ -129,4 +129,17 @@ program JobsGiveUpTest { jobs_test_st.day = 2 expect(jobs_offered(jobs_st, JOB_CATCH)) } + + test "two hundred jobs taken and given up, their facts drained each time, reuse a handful of records" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) { + fresh(jobs_st, jobs_test_st) + for k in 0 .. 200 { + jobs_take(jobs_st, JOB_CATCH) + count_of(jobs_st, 0) + jobs_give_up(jobs_st, JOB_CATCH) + count_of(jobs_st, 0) + jobs_st.jobs__back[JOB_CATCH] = -1 + } + print(`jobs: 200 taken and given up, {jobs_fact_records(jobs_st)} fact records made`) + expect(jobs_fact_records(jobs_st) < 10) + } } diff --git a/packages/ludic.minimap/README.md b/packages/ludic.minimap/README.md index 5439cf5a..2dae73b3 100644 --- a/packages/ludic.minimap/README.md +++ b/packages/ludic.minimap/README.md @@ -60,7 +60,7 @@ export port MinimapWorld { # every member has a default | `minimap_pin_count()`, `minimap_pin_max()`, `minimap_pin_full()`, `minimap_pin(i) -> MinimapPin` | the marks | | `minimap_pin_set_label/icon/colour(i, v)`, `minimap_pin_last_icon()` | editing one | | `minimap_pin_open()`, `minimap_pin_select(i)`, `minimap_pin_followed()`, `minimap_pin_follow(i)`, `minimap_pin_follow_toggle()` | the open one and the followed one | -| `minimap_pins_near(x, z, n, within) -> []int` | the nearest marks, not the followed one (for a compass) | +| `minimap_pins_near(x, z, n, within) -> []int` | the nearest marks, not the followed one (for a compass), in a list the state keeps and refills: read it, do not keep it | | `minimap_config_view(span, zoom_min, zoom_max, focus)`, `minimap_window(x0, y0, size)` | the view's range, and where the window is this frame | | `minimap_px(x)`, `minimap_py(z)`, `minimap_wx(rx)`, `minimap_wz(ry)`, `minimap_span()`, `minimap_scale()`, `minimap_scale_metres()` | projection, and a scale bar's round distance | | `minimap_zoom()`, `minimap_zoom_set(z)`, `minimap_zoom_step(dir)`, `minimap_zoom_about(rx, ry, factor)` | zoom | diff --git a/packages/ludic.minimap/grid.ludic b/packages/ludic.minimap/grid.ludic index fb0689a0..0299249b 100644 --- a/packages/ludic.minimap/grid.ludic +++ b/packages/ludic.minimap/grid.ludic @@ -1,6 +1,7 @@ # grid.ludic - the explored grid: square cells round the origin, and the part of it "explored" is # measured over (a core, so a bigger grid does not change what a share of it means) export state MinimapState { + mm_near: []int = new []int # minimap_pins_near's answer, cleared and refilled: a compass asks every frame mm_land_n: int = 0 # land cells in the core, counted once per origin mm_land_ox: float = 0.0 mm_land_oz: float = 0.0 diff --git a/packages/ludic.minimap/pins.ludic b/packages/ludic.minimap/pins.ludic index c6994dea..7df34b15 100644 --- a/packages/ludic.minimap/pins.ludic +++ b/packages/ludic.minimap/pins.ludic @@ -71,8 +71,9 @@ export function minimap_pin(minimap_st: MinimapState, i: int) -> MinimapPin { } # the nearest n marks within `within` of (x, z), nearest first, leaving out the followed one -export function minimap_pins_near(minimap_st: MinimapState, x: float, z: float, n: int, within: float) -> []int { - let out = new []int +export function minimap_pins_near(minimap_st: mut MinimapState, x: float, z: float, n: int, within: float) -> []int { + let out = minimap_st.mm_near + List.clear(out) for k in 0 .. n { var best = -1 var bd = within * within diff --git a/packages/ludic.minimap/tests/minimap_test.ludic b/packages/ludic.minimap/tests/minimap_test.ludic index f270b55a..645b1f77 100644 --- a/packages/ludic.minimap/tests/minimap_test.ludic +++ b/packages/ludic.minimap/tests/minimap_test.ludic @@ -111,6 +111,20 @@ program MinimapTest { expect_eq(near[1], 1) } + test "asking for the nearest marks every frame allocates nothing" (minimap_st: mut MinimapState, minimap_test_st: mut MinimapTestState) { + fresh(minimap_st, minimap_test_st) + minimap_config_pins(minimap_st, 8, 8, 5) + minimap_pin_add(minimap_st, 110.0, 100.0, 0, "near") + minimap_pin_add(minimap_st, 130.0, 100.0, 0, "next") + minimap_pins_near(minimap_st, 100.0, 100.0, 2, 400.0) + let before = Os.heap_bytes() + for k in 0 .. 1000 { minimap_pins_near(minimap_st, 100.0 + float(k % 7), 100.0, 2, 400.0) } + let grew = Os.heap_bytes() - before + print(`minimap: 1000 asks, heap {grew} bytes`) + expect(grew == long(0)) + expect_eq(len(minimap_pins_near(minimap_st, 100.0, 100.0, 2, 400.0)), 2) + } + test "the view: the world on the window and back, zoom about a point, a pan, focus and the clamp" (minimap_st: mut MinimapState, minimap_test_st: mut MinimapTestState) { fresh(minimap_st, minimap_test_st) minimap_centre(minimap_st, 100.0, 100.0) diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index 3ec7a04d..bb525d94 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -16,14 +16,15 @@ export function phys_is_open(physics_st: PhysicsState) -> bool { return physics_ # which world this is: a body, shape or walker id kept from an older one means nothing now export function phys_generation(physics_st: PhysicsState) -> int { return physics_st.ph_gen } -# everything let go: the world, every body in it and every shape (a world swap, a quit) +# everything let go: the world, every body in it and every shape (a world swap, a quit); the lists +# that held them are emptied and kept, since nothing is given back export function phys_close(physics_st: mut PhysicsState) -> void { if physics_st.ph_walkers != null { for i in 0 .. len(physics_st.ph_walkers) { if physics_st.ph_walkers[i] != null { jph_char_free(physics_st.ph_walkers[i]) } } } - physics_st.ph_walkers = new []pointer + if physics_st.ph_walkers != null { List.clear(physics_st.ph_walkers) } if physics_st.ph_world != null { jph_world_free(physics_st.ph_world) } physics_st.ph_world = null if physics_st.ph_shapes != null { @@ -31,7 +32,7 @@ export function phys_close(physics_st: mut PhysicsState) -> void { if physics_st.ph_shapes[i] != null { jph_shape_free(physics_st.ph_shapes[i]) } } } - physics_st.ph_shapes = new []pointer + if physics_st.ph_shapes != null { List.clear(physics_st.ph_shapes) } List.clear(physics_st.ph_spare) List.clear(physics_st.ph_floats) List.clear(physics_st.ph_float_k) diff --git a/packages/ludic.physics/tests/reuse_test.ludic b/packages/ludic.physics/tests/reuse_test.ludic new file mode 100644 index 00000000..ecbe8bcd --- /dev/null +++ b/packages/ludic.physics/tests/reuse_test.ludic @@ -0,0 +1,42 @@ +# reuse_test.ludic - what is taken away is given back: a walker made and removed two hundred times +# holds one place in the list and no more heap, and a world closed and opened again keeps its lists +import "ludic.physics" +import "ludic.base" +program ReuseTest { + numbers float + + test "a walker made and removed takes the same place and grows nothing" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + let w = phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + phys_walker_remove(physics_st, w) + let before = Os.heap_bytes() + for k in 0 .. 200 { + let id = phys_walker_add(physics_st, 0.4, 1.8, float(k), 0.0, 0.0, 50.0, 80.0) + expect_eq(id, w) + phys_walker_remove(physics_st, id) + } + let grew = Os.heap_bytes() - before + print(`reuse: 200 walkers made and removed, {len(physics_st.ph_walkers)} place, heap {grew} bytes`) + expect_eq(len(physics_st.ph_walkers), 1) + expect(grew < long(1024)) + phys_close(physics_st) + } + + test "a world closed and opened again keeps the lists it held" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + phys_box(physics_st, 1.0, 1.0, 1.0) + phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + let shapes = physics_st.ph_shapes + let walkers = physics_st.ph_walkers + for k in 0 .. 20 { + phys_close(physics_st) + expect(phys_open(physics_st, 64, 1)) + phys_box(physics_st, 1.0, 1.0, 1.0) + phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + } + expect(physics_st.ph_shapes == shapes) + expect(physics_st.ph_walkers == walkers) + expect_eq(phys_shapes(physics_st), 1) + phys_close(physics_st) + } +} diff --git a/packages/ludic.physics/walker.ludic b/packages/ludic.physics/walker.ludic index eb439790..c77c5776 100644 --- a/packages/ludic.physics/walker.ludic +++ b/packages/ludic.physics/walker.ludic @@ -27,7 +27,15 @@ function ph_walker(physics_st: PhysicsState, id: int) -> pointer { export function phys_walker_add(physics_st: mut PhysicsState, r: float, h: float, x: float, y: float, z: float, max_slope: float, mass: float) -> int { if physics_st.ph_world == null { return -1 } ph_buffers() - push(physics_st.ph_walkers, jph_char_new(physics_st.ph_world, r, h, x, y, z, max_slope, mass)) + let k = jph_char_new(physics_st.ph_world, r, h, x, y, z, max_slope, mass) + # a removed walker's place is taken again, so a horse called and sent away grows nothing + for i in 0 .. len(physics_st.ph_walkers) { + if physics_st.ph_walkers[i] == null { + physics_st.ph_walkers[i] = k + return i + } + } + push(physics_st.ph_walkers, k) return len(physics_st.ph_walkers) - 1 }