From 1ba869b644b1e5643118ba5e93892d34f3f53f75 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Fri, 29 Sep 2023 01:10:16 -0700 Subject: [PATCH] start replacing arrays with intrusive slists `add` costs ten cycles. `push_back` isn't actually any better, but bury_the_dead can get pretty bad, especially for large arrays (like the bullets collections). also replacing the kill loop structure with the `strip` call removes a massive amount of code repetition that's costing me a lot of tokens. I think the final result is _probably_ actually slower because of function call overhead per iteration except when there are collisions on many frames; hopefully the headroom bought by the bucket collider is enough because I'm definitely going to need the tokens. --- updatedshmup.p8 | 271 ++++++++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 103 deletions(-) diff --git a/updatedshmup.p8 b/updatedshmup.p8 index be8b018..612f552 100644 --- a/updatedshmup.p8 +++ b/updatedshmup.p8 @@ -21,6 +21,95 @@ function csv(s) return ret end +-- generate standard "overlay" +-- constructor for type tt. +-- if more is defined, generated +-- new calls more(ret) after +-- ret is definitely not nil +-- before calling setmetatable. +-- use to initialize mutables. +function mknew(tt, more) + local mt = {__index=tt} + -- check "more" only once ever + if more then + tt.new = function(ret) + if (not ret) ret = {} + more(ret) + setmetatable(ret, mt) + return ret + end + else + tt.new=function(ret) + if (not ret) ret = {} + setmetatable(ret, mt) + return ret + end + end +end + +linked_list = {} +mknew(linked_list, function(x) + x.next=nil + x.tail=x + end) + +function linked_list:push_back(x) + self.tail.next = x + self.tail = x +end + +function linked_list:push_front(x) + if (not self.next) self.tail = x + x.next = self.next + self.next = x +end + +-- vore eats another linked list +-- by appending its contents. +-- the ingested linked +-- list is no longer valid. +function linked_list:vore(x) + if (not x.next) return + self.tail.next = x.next + self.tail = x.tail +end + +-- strip calls f(x) for each +-- node, removing each node for +-- which f(x) returns true. it +-- returns the new tail; nil +-- if the list is now empty. +function linked_list:strip(f) + local p, n = self, self.next + while n do + if f(n) then + p.next = n.next + else + p = n + end + n = n.next + end + self.tail = p + return p +end + +function linked_list:pop_front() + local ret = self.next + if (not ret) return + self.next = ret.next + if (not ret.next) ret.tail = nil + return ret +end + +function linked_list:fix_tail() + local p, n = self, self.next + while n do + p = n + n = n.next + end + self.tail = p +end + function _init() init_bullet_mt() init_powerup_mt() @@ -47,125 +136,99 @@ function init_hpcols() hpcols = hpcols_lut[min(primary_ship.maxhp,6)] end +function new_linked() + local ret = {} + ret.tail = ret + return ret +end + function wipe_level() primary_ship = new_p1() init_hpcols() - pships = {primary_ship} - eships = {} - pbullets = {} - ebullets = {} - intangibles_fg = {} - intangibles_bg = {} - events = {} + pships = linked_list:new() + pships:push_back(primary_ship) + eships = linked_list:new() + pbullets = linked_list:new() + ebullets = linked_list:new() + intangibles_fg = linked_list:new() + intangibles_bg = linked_list:new() + events = linked_list:new() end function _update60() updategame() end +function call_f(x) + return x:f() +end + +function call_move(x) + return x:move() +end + function updategame() leveldone = level_frame() - new_events = {} - local deaths = {} - for i, e in ipairs(events) do - if (e()) add(deaths, i) - end - bury_the_dead(events, deaths) - foreach(new_events, function(e) - add(events, e) - end) - deaths = {} - for i, x in ipairs(intangibles_bg) do - if (x:move()) add(deaths, i) - end - bury_the_dead(intangibles_bg, deaths) - for _, tbl in ipairs({pships, eships, pbullets, ebullets, intangibles}) do - local deaths = {} - for i, x in ipairs(tbl) do - if (x:move()) add(deaths, i) - end - bury_the_dead(tbl, deaths) - end - --then, calculate collisions - local pdeaths = {} - local edeaths = {} - local eskips = {} - -- todo: always use a collider, - -- it saves if pships is as low as 2s - -- pships usually contians 1 thing, - -- so don't bother with a bucket collider - for ip, ps in ipairs(pships) do - for ie, es in ipairs(eships) do - if not eskips[ie] then - if collides(hurtbox(ps), hurtbox(es)) then - if (es:hitship(ps)) then - add(edeaths, ie) - eskips[ie] = true - end - if ps:hitship(es) then - add(pdeaths, ip) - break - end - end - end - end - end - bury_the_dead(pships, pdeaths) - bury_the_dead(eships, edeaths) - pdeaths = {} - edeaths = {} - eskips = {} - for ip, ps in ipairs(pships) do - for ie, eb in ipairs(ebullets) do - if not eskips[ie] then - if collides(hurtbox(ps), hurtbox(eb)) then - local dead = false - if ps:hitbullet(eb) then - add(pdeaths, ip) - dead = true - end - if (eb:hitship(ps)) then - add(edeaths, ie) - eskips[ie] =true - end - if (dead) break - end - end - end - end - bury_the_dead(pships, pdeaths) - bury_the_dead(ebullets, edeaths) - pdeaths = {} - edeaths = {} + new_events = new_linked() + events:strip(call_f) + events:vore(new_events) + for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do + lst:strip(call_move) + end + + pships:strip( + function(ps) + local pbox, pded = hurtbox(ps), false + eships:strip( + function(es) + if (~collides(pbox, hurtbox(es))) return + pded = pded or ps:hitship(es) + return es:hitship(ps) + end + ) + return pded + end + ) + pships:strip( + function(ps) + local pbox, pded = hurtbox(ps), false + ebullets:strip( + function(eb) + if (~collides(pbox, hurtbox(eb))) return + pded = pded or ps:hitbullet(eb) + return eb:hitship(ps) + end + ) + return pded + end + ) + -- many bullets and many enemy ships; -- use bucket collider for efficiency - local pbullet_collider = new_collider() - for idx, pb in ipairs(pbullets) do - pb.___pbullets_idx = idx - pbullet_collider:insert(pb) + local pbullet_collider = new_collider() + local p, n := pbullets, pbullets.next + while n do + n.prev = p + pbullet_collider:insert(n) + p = n + n = p.next end - - for es_idx, es in ipairs(eships) do - for pb in all(pbullet_collider:get_collisions(es)) do - local dead = false - if es:hitbullet(pb) then - add(edeaths, es_idx) - dead=true + + eships:strip( + function(es) + for pb in all(pbullet_collider:get_collisions(es)) do + if pb:hitship(es) then + pbullet_collider:hide(pb) + pb.prev.next = pb.next + if (pb.next) pb.next.prev = pb.prev + end + if (es:hitbullet(pb)) return true end - if pb:hitship(es) then - add(pdeaths, pb.___pbullets_idx) - pbullet_collider:hide(pb) - end - if (dead) break end - end - bury_the_dead(eships, edeaths) - bury_the_dead(pbullets, pdeaths) - for i, x in ipairs(intangibles_fg) do - if (x:move()) add(deaths, i) - end - bury_the_dead(intangibles_fg, deaths) - + ) + + intangibles_fg:strip(call_move) + if leveldone and ((#eships + #ebullets + #events) == 0) then state = win end @@ -174,6 +237,8 @@ function updategame() end end +-- XXX BOOKMARK XXX + function _draw() drawgame() draw_debug()