Quick answer: Static initializers that reference each other create cycles. Use lazy init: assign a placeholder, populate on first access.
A “Database” struct uses static fields to define enemy templates. Each template references shared assets via another struct. Stack overflow at startup — circular initialization.
Lazy Init Pattern
function EnemyDB() constructor {
static _cache = undefined;
static get_all = function() {
if (is_undefined(_cache)) {
_cache = {
goblin: new EnemyTemplate(...),
orc: new EnemyTemplate(...),
};
}
return _cache;
};
}
First call builds; subsequent calls return cache. No initialization until needed.
Avoid Cyclic Static Refs
If TemplateA references TemplateB at static init time, and TemplateB references TemplateA, you have a cycle. Break by deferring — one reference becomes “set later”.
Init Function Approach
function init_database() {
global.enemies = {};
global.enemies.goblin = new EnemyTemplate(...);
global.enemies.orc = new EnemyTemplate(...);
// fill cross-refs after both exist
global.enemies.goblin.boss_form = global.enemies.orc;
}
// Call once in obj_init Create
init_database();
Imperative init: declare all then wire up. No static initialization cycles.
Verifying
Game starts without stack overflow. Database accessible from any object after init. First access has small latency (lazy), subsequent free.
“Cycles in static init are unrecoverable. Defer one side via lazy init or imperative setup.”
For data-heavy projects, consider keeping database as JSON loaded at startup — sidesteps init order entirely.