🔍 Module Scanned
modules/world-worldgen/ (automated audit scan)
📝 Summary
The OverworldGenerator.deinit() function at line 87-89 of overworld_generator.zig is an empty no-op (_ = self;), but the struct owns owned resources including a ClassificationCache (256×256 grid = 16KB of inline data) and a sync.Mutex. This causes a memory leak when the generator is destroyed via the Generator interface.
📍 Location
- File:
modules/worldgen-overworld/src/overworld_generator.zig:87
- Function/Scope:
OverworldGenerator.deinit
🔴 Severity: High
- High: Memory leaks, race conditions, incorrect rendering, broken features
💥 Impact
When an OverworldGenerator is destroyed (via generator.deinit(allocator)), the ClassificationCache array and cache_mutex are never cleaned up. Since OverworldGenerator is heap-allocated by the registry and destroyed via the erased Generator interface pointer, this leak happens every time a world is unloaded or a generator is replaced.
🔎 Evidence
pub const OverworldGenerator = struct {
allocator: std.mem.Allocator,
classification_cache: ClassificationCache, // 256*256*ClassCell inline array (~16KB)
cache_center_x: i32,
cache_center_z: i32,
cache_mutex: Mutex,
terrain_shape: TerrainShapeGenerator,
biome_decorator: BiomeDecorator,
basic_chunks_only: bool,
// ...
};
pub fn deinit(self: *OverworldGenerator) void {
_ = self; // <-- NO-OP: ClassificationCache and cache_mutex never cleaned up
}
The Generator vtable calls this through an erased pointer:
fn deinitWrapper(ptr: *anyopaque, allocator: std.mem.Allocator) void {
const self: *OverworldGenerator = @ptrCast(@alignCast(ptr));
allocator.destroy(self); // Does NOT call .deinit() first!
}
🛠️ Proposed Fix
Implement proper cleanup in deinit():
pub fn deinit(self: *OverworldGenerator) void {
self.cache_mutex.lock();
defer self.cache_mutex.unlock();
// ClassificationCache stores no pointers - its ~16KB is inline and freed with the struct
// But we should still invalidate it to be safe
self.classification_cache.recenter(0, 0);
}
Or better, use Zig's cleanup syntax:
pub fn deinit(self: *OverworldGenerator) void {
self.classification_cache.recenter(0, 0);
}
Note: ClassificationCache stores no heap pointers (just 256×256 inline ?ClassCell values = ~16KB stack/inline), so it doesn't need explicit deinit(). However, the deinit() function should at minimum be a no-op that does something visible to confirm it was called, or the design should be documented that deinit() is intentionally empty because all owned data is inline.
✅ Acceptance Criteria
📚 References
Generator interface in modules/worldgen-api/src/root.zig:77-119
Generator.deinit vtable call pattern in modules/worldgen-overworld/src/overworld_generator.zig:1077-1080
ClassificationCache definition in modules/worldgen-overworld/src/gen_region.zig:413-488
🔍 Module Scanned
modules/world-worldgen/(automated audit scan)📝 Summary
The
OverworldGenerator.deinit()function at line 87-89 ofoverworld_generator.zigis an empty no-op (_ = self;), but the struct owns owned resources including aClassificationCache(256×256 grid = 16KB of inline data) and async.Mutex. This causes a memory leak when the generator is destroyed via theGeneratorinterface.📍 Location
modules/worldgen-overworld/src/overworld_generator.zig:87OverworldGenerator.deinit🔴 Severity: High
💥 Impact
When an
OverworldGeneratoris destroyed (viagenerator.deinit(allocator)), theClassificationCachearray andcache_mutexare never cleaned up. SinceOverworldGeneratoris heap-allocated by the registry and destroyed via the erasedGeneratorinterface pointer, this leak happens every time a world is unloaded or a generator is replaced.🔎 Evidence
The
Generatorvtable calls this through an erased pointer:🛠️ Proposed Fix
Implement proper cleanup in
deinit():Or better, use Zig's cleanup syntax:
Note:
ClassificationCachestores no heap pointers (just 256×256 inline?ClassCellvalues = ~16KB stack/inline), so it doesn't need explicitdeinit(). However, thedeinit()function should at minimum be a no-op that does something visible to confirm it was called, or the design should be documented thatdeinit()is intentionally empty because all owned data is inline.✅ Acceptance Criteria
OverworldGenerator.deinit()properly cleans up or documents why cleanup is unnecessaryGenerator.deinit()nix develop --command zig build test📚 References
Generatorinterface inmodules/worldgen-api/src/root.zig:77-119Generator.deinitvtable call pattern inmodules/worldgen-overworld/src/overworld_generator.zig:1077-1080ClassificationCachedefinition inmodules/worldgen-overworld/src/gen_region.zig:413-488