Add Structure Building Block asset loading (#1240)

* SBB

* Fix rotation code

* Remove rotate command

* Fix segfault during generation

* Delete format.bat

* Pregenerate all rotated blueprints

* Apply review suggestions

* Apply review suggestions

* Change child block storage model

* Resolve child structures during load

* Move structure_building_blocks.zig to terrain

* Update src/rotation.zig

* Apply review suggestions

* Remove sbb blocks

* Remove SBBGen

* Remove example SBB

* Remove blueprint code

* Remove createAssetStringID

* Apply review suggestions

* Apply review suggestions

* Add asset ID generation

* Revert "Add asset ID generation"

This reverts commit ffe8fd281486f2124ab83b0e614b6a7db97a020d.

* Remove SBB loading

* Revert "Remove SBB loading"

This reverts commit 7eabad906dd05e3c128115b824986ff96815ac03.

* Apply review requests

* Restore base.zig.zon
This commit is contained in:
Krzysztof Wiśniewski 2025-03-28 20:05:46 +01:00 committed by GitHub
parent d9dc3f1a22
commit 21409df5a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 3 deletions

View File

@ -21,6 +21,7 @@ var commonBiomes: std.StringHashMap(ZonElement) = undefined;
var commonBiomeMigrations: std.StringHashMap(ZonElement) = undefined;
var commonRecipes: std.StringHashMap(ZonElement) = undefined;
var commonModels: std.StringHashMap([]const u8) = undefined;
var commonStructureBuildingBlocks: std.StringHashMap(ZonElement) = undefined;
var commonBlueprints: std.StringHashMap([]u8) = undefined;
pub fn init() void {
@ -37,6 +38,7 @@ pub fn init() void {
commonBiomeMigrations = .init(arenaAllocator.allocator);
commonRecipes = .init(arenaAllocator.allocator);
commonModels = .init(arenaAllocator.allocator);
commonStructureBuildingBlocks = .init(arenaAllocator.allocator);
commonBlueprints = .init(arenaAllocator.allocator);
readAssets(
@ -50,12 +52,13 @@ pub fn init() void {
&commonBiomeMigrations,
&commonRecipes,
&commonModels,
&commonStructureBuildingBlocks,
&commonBlueprints,
);
std.log.info(
"Finished assets init with {} blocks ({} migrations), {} items, {} tools, {} biomes ({} migrations), {} recipes, and {} blueprints",
.{commonBlocks.count(), commonBlockMigrations.count(), commonItems.count(), commonTools.count(), commonBiomes.count(), commonBiomeMigrations.count(), commonRecipes.count(), commonBlueprints.count()},
"Finished assets init with {} blocks ({} migrations), {} items, {} tools, {} biomes ({} migrations), {} recipes, {} structure building blocks and {} blueprints",
.{commonBlocks.count(), commonBlockMigrations.count(), commonItems.count(), commonTools.count(), commonBiomes.count(), commonBiomeMigrations.count(), commonRecipes.count(), commonStructureBuildingBlocks.count(), commonBlueprints.count()},
);
}
@ -267,6 +270,7 @@ pub fn readAssets(
biomeMigrations: *std.StringHashMap(ZonElement),
recipes: *std.StringHashMap(ZonElement),
models: *std.StringHashMap([]const u8),
structureBuildingBlocks: *std.StringHashMap(ZonElement),
blueprints: *std.StringHashMap([]u8),
) void {
var addons = main.List(std.fs.Dir).init(main.stackAllocator);
@ -305,6 +309,7 @@ pub fn readAssets(
readAllZonFilesInAddons(externalAllocator, addons, addonNames, "biomes", true, biomes, biomeMigrations);
readAllZonFilesInAddons(externalAllocator, addons, addonNames, "recipes", false, recipes, null);
readAllObjFilesInAddonsHashmap(externalAllocator, addons, addonNames, "models", models);
readAllZonFilesInAddons(externalAllocator, addons, addonNames, "sbb", true, structureBuildingBlocks, null);
readAllBlueprintFilesInAddons(externalAllocator, addons, addonNames, "blueprints", blueprints);
}
@ -472,6 +477,8 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale
defer recipes.clearAndFree();
var models = commonModels.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable;
defer models.clearAndFree();
var structureBuildingBlocks = commonStructureBuildingBlocks.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable;
defer structureBuildingBlocks.clearAndFree();
var blueprints = commonBlueprints.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable;
defer blueprints.clearAndFree();
@ -486,6 +493,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale
&biomeMigrations,
&recipes,
&models,
&structureBuildingBlocks,
&blueprints,
);
errdefer unloadAssets();
@ -594,6 +602,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale
}
try sbb.registerBlueprints(&blueprints);
try sbb.registerSBB(&structureBuildingBlocks);
// Biomes:
var nextBiomeNumericId: u32 = 0;

View File

@ -14,6 +14,7 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator;
var arena = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator);
const arenaAllocator = arena.allocator();
var structureCache: std.StringHashMapUnmanaged(StructureBuildingBlock) = .{};
var blueprintCache: std.StringHashMapUnmanaged(*[4]BlueprintEntry) = .{};
var childrenToResolve: List(struct {parentId: []const u8, colorName: []const u8, colorIndex: usize, childIndex: usize, structureId: []const u8}) = undefined;
@ -101,6 +102,103 @@ pub fn isOriginBlock(block: Block) bool {
return block.typ == originBlockNumericId;
}
pub const StructureBuildingBlock = struct {
children: []AliasTable(Child),
blueprints: *[4]BlueprintEntry,
fn initFromZon(stringId: []const u8, zon: ZonElement) !StructureBuildingBlock {
const blueprintId = zon.get(?[]const u8, "blueprint", null) orelse {
std.log.err("['{s}'] Missing blueprint field.", .{stringId});
return error.MissingBlueprintIdField;
};
const blueprints = blueprintCache.get(blueprintId) orelse {
std.log.err("['{s}'] Could not find blueprint '{s}'.", .{stringId, blueprintId});
return error.MissingBlueprint;
};
const self = StructureBuildingBlock{
.children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len),
.blueprints = blueprints,
};
const childrenZon = zon.getChild("children");
for(childBlockStringId.items, 0..) |colorName, colorIndex| {
self.children[colorIndex] = try initChildTableFromZon(stringId, colorName, colorIndex, childrenZon.getChild(colorName));
}
return self;
}
pub fn getBlueprint(self: StructureBuildingBlock, rotation: Degrees) *BlueprintEntry {
return &self.blueprints[@intFromEnum(rotation)];
}
pub fn pickChild(self: StructureBuildingBlock, block: BlueprintEntry.StructureBlock, seed: *u64) *const StructureBuildingBlock {
return self.children[block.index].sample(seed).structure;
}
};
fn initChildTableFromZon(parentId: []const u8, colorName: []const u8, colorIndex: usize, zon: ZonElement) !AliasTable(Child) {
if(zon == .null) return .init(arenaAllocator, &.{});
if(zon != .array) {
std.log.err("['{s}'->'{s}'] Incorrect child data structure, array expected.", .{parentId, colorName});
return .init(arenaAllocator, &.{});
}
if(zon.array.items.len == 0) {
std.log.err("['{s}'->'{s}'] Empty children list.", .{parentId, colorName});
return error.EmptyChildrenList;
}
const list = arenaAllocator.alloc(Child, zon.array.items.len);
for(zon.array.items, 0..) |entry, childIndex| {
list[childIndex] = try Child.initFromZon(parentId, colorName, colorIndex, childIndex, entry);
}
return .init(arenaAllocator, list);
}
const Child = struct {
structure: *StructureBuildingBlock,
chance: f32,
fn initFromZon(parentId: []const u8, colorName: []const u8, colorIndex: usize, childIndex: usize, zon: ZonElement) !Child {
const structureId = zon.get([]const u8, "structure", "");
if(structureId.len == 0) {
std.log.err("['{s}'->'{s}'->'{d}'] Child node has empty structure field, parent structure will be discarded.", .{parentId, colorName, childIndex});
return error.EmptyStructureId;
}
childrenToResolve.append(.{.parentId = parentId, .colorName = colorName, .colorIndex = colorIndex, .childIndex = childIndex, .structureId = structureId});
return .{
.structure = undefined,
.chance = zon.get(f32, "chance", 1.0),
};
}
};
pub fn registerSBB(structures: *std.StringHashMap(ZonElement)) !void {
std.debug.assert(structureCache.capacity() == 0);
structureCache.ensureTotalCapacity(arenaAllocator.allocator, structures.count()) catch unreachable;
childrenToResolve = .init(main.stackAllocator);
{
var iterator = structures.iterator();
while(iterator.next()) |entry| {
const value = StructureBuildingBlock.initFromZon(entry.key_ptr.*, entry.value_ptr.*) catch |err| {
std.log.err("Could not register structure building block '{s}' ({s})", .{entry.key_ptr.*, @errorName(err)});
continue;
};
const key = arenaAllocator.dupe(u8, entry.key_ptr.*);
structureCache.put(arenaAllocator.allocator, key, value) catch unreachable;
std.log.debug("Registered structure building block: '{s}'", .{entry.key_ptr.*});
}
}
{
for(childrenToResolve.items) |entry| {
const parent = structureCache.getPtr(entry.parentId).?;
const child = structureCache.getPtr(entry.structureId) orelse {
std.log.err("Could not find child structure '{s}' for child resolution.", .{entry.structureId});
continue;
};
std.log.debug("Resolved child structure '{s}'->'{s}'->'{d}' to '{s}'", .{entry.parentId, entry.colorName, entry.childIndex, entry.structureId});
parent.children[entry.colorIndex].items[entry.childIndex].structure = child;
}
childrenToResolve.deinit();
}
}
pub fn registerChildBlock(numericId: u16, stringId: []const u8) void {
const index: u16 = @intCast(childBlockNumericIdMap.count());
childBlockNumericIdMap.put(arenaAllocator.allocator, numericId, index) catch unreachable;
@ -123,7 +221,7 @@ pub fn registerBlueprints(blueprints: *std.StringHashMap([]u8)) !void {
while(iterator.next()) |entry| {
const stringId = entry.key_ptr.*;
const blueprint0 = Blueprint.load(arenaAllocator, entry.value_ptr.*) catch |err| {
std.log.err("Could not load blueprint {s}: {s}", .{stringId, @errorName(err)});
std.log.err("Could not load blueprint '{s}' ({s})", .{stringId, @errorName(err)});
continue;
};
@ -140,9 +238,14 @@ pub fn registerBlueprints(blueprints: *std.StringHashMap([]u8)) !void {
}
}
pub fn getByStringId(stringId: []const u8) ?*StructureBuildingBlock {
return structureCache.getPtr(stringId);
}
pub fn reset() void {
childBlockNumericIdMap = .{};
childBlockStringId = .{};
structureCache = .{};
blueprintCache = .{};
_ = arena.reset(.free_all);