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.
This commit is contained in:
Kistaro Windrider 2023-09-29 01:10:16 -07:00
parent bd67006e3c
commit 1ba869b644
Signed by: kistaro
SSH Key Fingerprint: SHA256:TBE2ynfmJqsAf0CP6gsflA0q5X5wD5fVKWPsZ7eVUg8

View File

@ -21,6 +21,95 @@ function csv(s)
return ret return ret
end 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() function _init()
init_bullet_mt() init_bullet_mt()
init_powerup_mt() init_powerup_mt()
@ -47,124 +136,98 @@ function init_hpcols()
hpcols = hpcols_lut[min(primary_ship.maxhp,6)] hpcols = hpcols_lut[min(primary_ship.maxhp,6)]
end end
function new_linked()
local ret = {}
ret.tail = ret
return ret
end
function wipe_level() function wipe_level()
primary_ship = new_p1() primary_ship = new_p1()
init_hpcols() init_hpcols()
pships = {primary_ship} pships = linked_list:new()
eships = {} pships:push_back(primary_ship)
pbullets = {} eships = linked_list:new()
ebullets = {} pbullets = linked_list:new()
intangibles_fg = {} ebullets = linked_list:new()
intangibles_bg = {} intangibles_fg = linked_list:new()
events = {} intangibles_bg = linked_list:new()
events = linked_list:new()
end end
function _update60() function _update60()
updategame() updategame()
end end
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end
function updategame() function updategame()
leveldone = level_frame() leveldone = level_frame()
new_events = {} new_events = new_linked()
local deaths = {} events:strip(call_f)
for i, e in ipairs(events) do events:vore(new_events)
if (e()) add(deaths, i) for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do
end lst:strip(call_move)
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 = {}
-- 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)
end end
for es_idx, es in ipairs(eships) do pships:strip(
for pb in all(pbullet_collider:get_collisions(es)) do function(ps)
local dead = false local pbox, pded = hurtbox(ps), false
if es:hitbullet(pb) then eships:strip(
add(edeaths, es_idx) function(es)
dead=true if (~collides(pbox, hurtbox(es))) return
end pded = pded or ps:hitship(es)
if pb:hitship(es) then return es:hitship(ps)
add(pdeaths, pb.___pbullets_idx) end
pbullet_collider:hide(pb) )
end return pded
if (dead) break
end 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()
local p, n := pbullets, pbullets.next
while n do
n.prev = p
pbullet_collider:insert(n)
p = n
n = p.next
end end
bury_the_dead(eships, edeaths)
bury_the_dead(pbullets, pdeaths) eships:strip(
for i, x in ipairs(intangibles_fg) do function(es)
if (x:move()) add(deaths, i) for pb in all(pbullet_collider:get_collisions(es)) do
end if pb:hitship(es) then
bury_the_dead(intangibles_fg, deaths) 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
end
)
intangibles_fg:strip(call_move)
if leveldone and ((#eships + #ebullets + #events) == 0) then if leveldone and ((#eships + #ebullets + #events) == 0) then
state = win state = win
@ -174,6 +237,8 @@ function updategame()
end end
end end
-- XXX BOOKMARK XXX
function _draw() function _draw()
drawgame() drawgame()
draw_debug() draw_debug()