Allow direct use of blueprints as SBBs (#1500)

## Descriptions

This pull request adds inline SBBs feature which allows blueprints to be
used directly (without SBB zon file) as child in other SBB files.
Blueprint used this way must not any child blocks on it's own.
To ensure that the feature works correctly some of the now redundant SBB
zon files were removed.
Current implementation generates SBB at runtime for each of the
blueprints that has 0 child blocks and doesn't have an SBB with same ID.
In the future the implementation could be changed to create SBBs on
demand, to avoid wasting memory on blueprints which are not used or use
SBB with different name, that is not critical tho (really small gains)
and requires #1499

## Links

Resolves: #1403

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
Krzysztof Wiśniewski 2025-06-01 12:56:54 +02:00 committed by GitHub
parent 74b0fb4431
commit 8f9ebe55fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 107 additions and 126 deletions

View File

@ -21,7 +21,7 @@
}, },
.{ .{
.id = "cubyz:sbb", .id = "cubyz:sbb",
.structure = "cubyz:tree/birch/forest_generator", .structure = "cubyz:tree/birch/mixed_forest_generator",
.placeMode = .degradable, .placeMode = .degradable,
.chance = 0.1, .chance = 0.1,
}, },

View File

@ -25,7 +25,7 @@
}, },
.{ .{
.id = "cubyz:sbb", .id = "cubyz:sbb",
.structure = "cubyz:tree/birch/birch_forest_generator", .structure = "cubyz:tree/birch/forest_generator",
.placeMode = .degradable, .placeMode = .degradable,
.chance = 0.2, .chance = 0.2,
}, },

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/root/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/root/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/root/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/small_branch/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/small_branch/2",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/small_branch/3",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/stub/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/stub/2",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/stub/3",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/stub/4",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/top/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/birch/1/top/2",
.children = .{},
}

View File

@ -1,11 +0,0 @@
.{
.blueprint = "cubyz:generator",
.children = .{
.crimson = .{
.{.structure = "cubyz:tree/birch/1/tree/1", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/2", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/3", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/4", .chance = 0.4},
},
},
}

View File

@ -1,11 +1,11 @@
.{ .{
.blueprint = "cubyz:generator", .blueprint = "cubyz:tree/birch/forest_generator",
.children = .{ .children = .{
.crimson = .{ .crimson = .{
.{.structure = "cubyz:tree/birch/1/tree/1"}, .{.structure = "cubyz:tree/birch/1/tree/1", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/2"}, .{.structure = "cubyz:tree/birch/1/tree/2", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/3"}, .{.structure = "cubyz:tree/birch/1/tree/3", .chance = 0.2},
.{.structure = "cubyz:tree/birch/1/tree/4"}, .{.structure = "cubyz:tree/birch/1/tree/4", .chance = 0.4},
}, },
}, },
} }

View File

@ -0,0 +1,11 @@
.{
.blueprint = "cubyz:tree/birch/forest_generator",
.children = .{
.crimson = .{
.{.structure = "cubyz:tree/birch/1/tree/1"},
.{.structure = "cubyz:tree/birch/1/tree/2"},
.{.structure = "cubyz:tree/birch/1/tree/3"},
.{.structure = "cubyz:tree/birch/1/tree/4"},
},
},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/leaf/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/leaf/2",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/leaf/3",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/2",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/3",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/4",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/5",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/root/6",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/stub/1",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/stub/2",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/stub/3",
.children = .{},
}

View File

@ -1,4 +0,0 @@
.{
.blueprint = "cubyz:tree/oak/1/stub/4",
.children = .{},
}

Binary file not shown.

View File

@ -1,5 +1,5 @@
.{ .{
.blueprint = "cubyz:generator", .blueprint = "cubyz:tree/oak/forest_generator",
.children = .{ .children = .{
.crimson = .{ .crimson = .{
.{.structure = "cubyz:tree/oak/1/base/1", .chance = 0.5}, .{.structure = "cubyz:tree/oak/1/base/1", .chance = 0.5},

View File

@ -1,5 +1,5 @@
.{ .{
.blueprint = "cubyz:generator", .blueprint = "cubyz:tree/oak/forest_generator",
.children = .{ .children = .{
.crimson = .{ .crimson = .{
.{.structure = "cubyz:tree/oak/1/base/1"}, .{.structure = "cubyz:tree/oak/1/base/1"},

View File

@ -46,6 +46,10 @@ const BlueprintEntry = struct {
pub inline fn pos(self: StructureBlock) Vec3i { pub inline fn pos(self: StructureBlock) Vec3i {
return Vec3i{self.x, self.y, self.z}; return Vec3i{self.x, self.y, self.z};
} }
pub fn id(self: StructureBlock) []const u8 {
return childBlockStringId.items[self.index];
}
}; };
fn init(blueprint: Blueprint, stringId: []const u8) !BlueprintEntry { fn init(blueprint: Blueprint, stringId: []const u8) !BlueprintEntry {
@ -120,10 +124,14 @@ pub const StructureBuildingBlock = struct {
std.log.err("['{s}'] Missing blueprint field.", .{stringId}); std.log.err("['{s}'] Missing blueprint field.", .{stringId});
return error.MissingBlueprintIdField; return error.MissingBlueprintIdField;
}; };
const blueprints = blueprintCache.get(blueprintId) orelse { const blueprintsTemplate = blueprintCache.get(blueprintId) orelse {
std.log.err("['{s}'] Could not find blueprint '{s}'.", .{stringId, blueprintId}); std.log.err("['{s}'] Could not find blueprint '{s}'.", .{stringId, blueprintId});
return error.MissingBlueprint; return error.MissingBlueprint;
}; };
const blueprints = arenaAllocator.create([4]BlueprintEntry);
blueprints.* = blueprintsTemplate.*;
const self = StructureBuildingBlock{ const self = StructureBuildingBlock{
.id = stringId, .id = stringId,
.children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len), .children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len),
@ -133,8 +141,54 @@ pub const StructureBuildingBlock = struct {
for(childBlockStringId.items, 0..) |colorName, colorIndex| { for(childBlockStringId.items, 0..) |colorName, colorIndex| {
self.children[colorIndex] = try initChildTableFromZon(stringId, colorName, colorIndex, childrenZon.getChild(colorName)); self.children[colorIndex] = try initChildTableFromZon(stringId, colorName, colorIndex, childrenZon.getChild(colorName));
} }
self.updateBlueprintChildLists();
return self; return self;
} }
pub fn updateBlueprintChildLists(self: StructureBuildingBlock) void {
for(self.children, 0..) |child, index| found: {
if(child.items.len == 0) continue;
for(self.blueprints[0].childBlocks) |blueprintChild| {
if(blueprintChild.index != index) continue;
break :found;
}
std.log.err("['{s}'] Blueprint doesn't contain child '{s}' but configuration for it was specified.", .{self.id, childBlockStringId.items[index]});
}
for(self.blueprints, 0..) |*blueprint, index| {
var childBlocks: ListUnmanaged(BlueprintEntry.StructureBlock) = .{};
defer childBlocks.deinit(main.stackAllocator);
for(blueprint.childBlocks) |child| {
if(self.children[child.index].items.len == 0) {
if(index == 0) std.log.err("['{s}'] Missing child structure configuration for child '{s}'", .{self.id, child.id()});
continue;
}
childBlocks.append(main.stackAllocator, child);
}
blueprint.childBlocks = arenaAllocator.dupe(BlueprintEntry.StructureBlock, childBlocks.items);
}
}
pub fn initInline(sbbId: []const u8) !StructureBuildingBlock {
const blueprintsTemplate = blueprintCache.get(sbbId) orelse {
std.log.err("['{s}'] Could not find blueprint '{s}'.", .{sbbId, sbbId});
return error.MissingBlueprint;
};
const blueprints = arenaAllocator.create([4]BlueprintEntry);
blueprints.* = blueprintsTemplate.*;
for(blueprints, 0..) |*blueprint, index| {
if(index == 0) {
for(blueprint.childBlocks) |child| std.log.err("['{s}'] Missing child structure configuration for child '{s}'", .{sbbId, child.id()});
}
blueprint.childBlocks = &.{};
}
return .{
.id = sbbId,
.children = &.{},
.blueprints = blueprints,
};
}
pub fn getBlueprint(self: StructureBuildingBlock, rotation: Degrees) *BlueprintEntry { pub fn getBlueprint(self: StructureBuildingBlock, rotation: Degrees) *BlueprintEntry {
return &self.blueprints[@intFromEnum(rotation)]; return &self.blueprints[@intFromEnum(rotation)];
} }
@ -150,8 +204,8 @@ fn initChildTableFromZon(parentId: []const u8, colorName: []const u8, colorIndex
return .init(arenaAllocator, &.{}); return .init(arenaAllocator, &.{});
} }
if(zon.array.items.len == 0) { if(zon.array.items.len == 0) {
std.log.err("['{s}'->'{s}'] Empty children list.", .{parentId, colorName}); std.log.err("['{s}'->'{s}'] Empty children list not allowed. Remove 'children' field or add child structure configurations.", .{parentId, colorName});
return error.EmptyChildrenList; return .init(arenaAllocator, &.{});
} }
const list = arenaAllocator.alloc(Child, zon.array.items.len); const list = arenaAllocator.alloc(Child, zon.array.items.len);
for(zon.array.items, 0..) |entry, childIndex| { for(zon.array.items, 0..) |entry, childIndex| {
@ -193,16 +247,38 @@ pub fn registerSBB(structures: *Assets.ZonHashMap) !void {
std.log.debug("Registered structure building block: '{s}'", .{entry.key_ptr.*}); std.log.debug("Registered structure building block: '{s}'", .{entry.key_ptr.*});
} }
} }
{
var keyIterator = blueprintCache.keyIterator();
while(keyIterator.next()) |key_ptr| {
const blueprintId = key_ptr.*;
if(structureCache.contains(blueprintId)) continue;
const value = StructureBuildingBlock.initInline(blueprintId) catch |err| {
std.log.err("Could not register inline structure building block '{s}' ({s})", .{blueprintId, @errorName(err)});
continue;
};
const key = arenaAllocator.dupe(u8, blueprintId);
structureCache.put(arenaAllocator.allocator, key, value) catch unreachable;
std.log.debug("Registered inline structure building block: '{s}'", .{blueprintId});
}
}
{ {
for(childrenToResolve.items) |entry| { for(childrenToResolve.items) |entry| {
const parent = structureCache.getPtr(entry.parentId).?; const parent = structureCache.getPtr(entry.parentId).?;
const child = structureCache.getPtr(entry.structureId) orelse { const child = getByStringId(entry.structureId) orelse {
std.log.err("Could not find child structure '{s}' for child resolution.", .{entry.structureId}); std.log.err("Could not find child structure nor blueprint '{s}' for child resolution.", .{entry.structureId});
continue; continue;
}; };
if(parent.children.len <= entry.colorIndex) {
main.utils.panicWithMessage("Error resolving child structure '{s}'->'{s}'->'{d}' to '{s}'", .{entry.parentId, entry.colorName, entry.childIndex, entry.structureId});
}
const childColor = parent.children[entry.colorIndex];
std.log.debug("Resolved child structure '{s}'->'{s}'->'{d}' to '{s}'", .{entry.parentId, entry.colorName, entry.childIndex, entry.structureId}); 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; childColor.items[entry.childIndex].structure = child;
} }
} }
} }

View File

@ -2212,3 +2212,8 @@ test "NamedCallbacks registers functions" {
try std.testing.expectEqual(null, testFunctions.getFunctionPointer("functionTest")); try std.testing.expectEqual(null, testFunctions.getFunctionPointer("functionTest"));
try std.testing.expectEqual(null, testFunctions.getFunctionPointer("wrongSignatureFunction")); try std.testing.expectEqual(null, testFunctions.getFunctionPointer("wrongSignatureFunction"));
} }
pub fn panicWithMessage(comptime fmt: []const u8, args: anytype) void {
const message = std.fmt.allocPrint(main.stackAllocator.allocator, fmt, args) catch unreachable;
@panic(message);
}