Code: Select all
EmpireProperty.inhabited_planet_count = weak_memoize_1_to_n | function(empire)
local count = 0
for planet ; empire.planets
if planet.inhabited
count += 1
end
end
return count
end
Code: Select all
EmpireProperty.inhabited_planet_count = weak_memoize_1_to_n | function(empire)
local count = 0
for planet ; empire.planets
if planet.inhabited
count += 1
end
end
return count
end
Yes, that should work.harpy eagle wrote:I have a question about how the memoization functions work in the game. So suppose I wanted to cache the number of inhabited planets the current empire has this turn, to avoid iterating through empire.planets to count them every time. Would the following work?
SiS runs Lua with the automatic garbage collector turned off -- however, we do a manual garbage collection pass any time either 1) the player completes a strategic action or 2) the game window is resized.harpy eagle wrote:How does the memoizer know when the cache has become stale and the function needs to be called again?
If you're inside a situation where weak memoization isn't particularly safe you may need to use a different pattern. Because the AI executes inside a "throw away" temporary environment, you can actually use strong memoizers there to store temporaries that will only be valid for the current ai call. See, for example: navy_composition.lua:init_production_desire.ships().harpy eagle wrote:Would the following work?
Well, as long as any reference to the table is still around, it will still exist after the GC call. But (assuming you're using the n-return value memoization helpers), the function that created the table will create a new version of the same table the next time it's called.harpy eagle wrote:Since it sounds like all weak caches get flushed by the GC, what happens if a weakly memoized function returns a table that isn't discarded in time?
Yeah. Basically, at the start of an AI function call, you can add anything you like to the _ENV, and be confident that the variables you've created will only exist until the AI call completes. This includes creating new strong_memoized functions, which you'll then be able to call from other AI helpers with confidence that the associated return value caches will only last until the top-level AI call completes (because the functions *themselves* will only last that long).harpy eagle wrote:But strong memoization is fine for stuff being used by AI code.
Do they check if a table has been cached and only return copies?sven wrote:Well, as long as any reference to the table is still around, it will still exist after the GC call. But (assuming you're using the n-return value memoization helpers), the function that created the table will create a new version of the same table the next time it's called.
If you don't hit the cache, you'll rerun the whole function, which will generally create a new table from scratch.harpy eagle wrote:Do they check if a table has been cached and only return copies?
Code: Select all
local f = weak_memoizeNN | function(a) return {} end
local t1 = f(1) -- creates a new table for f(1)
t1.bar=2
local t2 = f(1) -- returns the f(1) table created previously
print(t2.bar) --> prints 2
Code: Select all
local get_ship_design = weak_memoize_1_to_1 | function(ship)
return Design.clone(ship.source_design)
end
function ship_power_score(ship)
local mult = 1
if ship.empire == empire
mult = 1 - ( (ship.armor_damage or 0 )+(ship.structure_damage or 0) ) / (ship.armor+ship.structure)
end
return mult*design_combat_value(get_ship_design | ship)
end
local design_to_specs = weak_memoize_1_to_1 | function(design)
return design.update_hull_specs()
end
function design_combat_value(design)
local specs = design_to_specs(design)
-- other code...
end
Yup. That will avoid repeated calculations of Design.clone() and design.update_hull_specs() when ship_power_score is repeatedly called for the same ship.harpy eagle wrote:Ok, so putting it all together, does this seem like a good use case for memoization?
Well, AIContexts can be tricky. Read the comment at the start of @AIContext.lua first, if you haven't yet. Then consider that the rules around accessing functions defined in AIContexts are actually dangerously permissive. When I write:harpy eagle wrote:I guess I can just use strong memoization then?
Code: Select all
_ENV = AIContext
function ship_power(ship)
return empire==ship.empire and 1 or 0
end
Code: Select all
local get_ship_design = weak_memoize_1_to_1 | function(ship)
return Design.clone(ship.source_design)
end
Code: Select all
function ship_power_score(ship)
local mult = 1
if ship.empire == empire
mult = 1 - ( (ship.armor_damage or 0 )+(ship.structure_damage or 0) ) / (ship.armor+ship.structure)
end
local design = get_ship_design | ship
if design.design_name ~= ship.source_design.design_name
local memoized = design_combat_value(design)
local fresh = design_combat_value | Design.clone(ship.source_design)
print(design.design_name, memoized, ship.source_design.design_name, fresh)
if memoized ~= fresh
error('possible memoization error?')
end
end
local combat_power = design_combat_value(design)
return mult*combat_power
end
Hmm. I usually avoid weak_memoize_1_to_1 because it can have weird caching behaviors (especially when used with objects, like ships). If you switch to weak_memoizeNN, does that fix the glitch?harpy eagle wrote:I seem to be having a bit of an issue with memoization.
Huh, well I guess it's time to switch all of my instances of weak_memoize_1_to_1 then (It seems I'm only using it with either ships or empires).sven wrote:Hmm. I usually avoid weak_memoize_1_to_1 because it can have weird caching behaviors (especially when used with objects, like ships).
I'll have to wait and see if it pops up again, it's not exactly easy to reproduce. That said... the moment I changed it the fleet AI started behaving very differently, so... maybe?sven wrote:If you switch to weak_memoizeNN, does that fix the glitch?