Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions src/assets.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const NeverFailingArenaAllocator = main.heap.NeverFailingArenaAllocator;
const ListUnmanaged = main.ListUnmanaged;
const files = main.files;
const structures_zig = main.server.terrain.structures;

var commonAssetArena: NeverFailingAllocator = undefined;
var common: Assets = undefined;
Expand All @@ -30,6 +31,8 @@ pub const Assets = struct {
tools: ZonHashMap,
biomes: ZonHashMap,
biomeMigrations: AddonNameToZonMap,
structureTables: ZonHashMap,
structureTableMigrations: AddonNameToZonMap,
recipes: ZonHashMap,
models: BytesHashMap,
structureBuildingBlocks: ZonHashMap,
Expand All @@ -45,6 +48,8 @@ pub const Assets = struct {
.tools = .{},
.biomes = .{},
.biomeMigrations = .{},
.structureTables = .{},
.structureTableMigrations = .{},
.recipes = .{},
.models = .{},
.structureBuildingBlocks = .{},
Expand All @@ -60,6 +65,8 @@ pub const Assets = struct {
self.tools.deinit(allocator.allocator);
self.biomes.deinit(allocator.allocator);
self.biomeMigrations.deinit(allocator.allocator);
self.structureTables.deinit(allocator.allocator);
self.structureTableMigrations.deinit(allocator.allocator);
self.recipes.deinit(allocator.allocator);
self.models.deinit(allocator.allocator);
self.structureBuildingBlocks.deinit(allocator.allocator);
Expand All @@ -75,6 +82,8 @@ pub const Assets = struct {
.tools = self.tools.clone(allocator.allocator) catch unreachable,
.biomes = self.biomes.clone(allocator.allocator) catch unreachable,
.biomeMigrations = self.biomeMigrations.clone(allocator.allocator) catch unreachable,
.structureTables = self.structureTables.clone(allocator.allocator) catch unreachable,
.structureTableMigrations = self.structureTables.clone(allocator.allocator) catch unreachable,
.recipes = self.recipes.clone(allocator.allocator) catch unreachable,
.models = self.models.clone(allocator.allocator) catch unreachable,
.structureBuildingBlocks = self.structureBuildingBlocks.clone(allocator.allocator) catch unreachable,
Expand All @@ -91,6 +100,7 @@ pub const Assets = struct {
addon.readAllZon(allocator, "blocks", true, &self.blocks, &self.blockMigrations);
addon.readAllZon(allocator, "items", true, &self.items, &self.itemMigrations);
addon.readAllZon(allocator, "tools", true, &self.tools, null);
addon.readAllZon(allocator, "structure_tables", true, &self.structureTables, &self.structureTableMigrations);
addon.readAllZon(allocator, "biomes", true, &self.biomes, &self.biomeMigrations);
addon.readAllZon(allocator, "recipes", false, &self.recipes, null);
addon.readAllZon(allocator, "sbb", true, &self.structureBuildingBlocks, null);
Expand All @@ -101,8 +111,8 @@ pub const Assets = struct {
}
fn log(self: *Assets, typ: enum {common, world}) void {
std.log.info(
"Finished {s} assets reading with {} blocks ({} migrations), {} items ({} migrations), {} tools, {} biomes ({} migrations), {} recipes, {} structure building blocks, {} blueprints and {} particles",
.{@tagName(typ), self.blocks.count(), self.blockMigrations.count(), self.items.count(), self.itemMigrations.count(), self.tools.count(), self.biomes.count(), self.biomeMigrations.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()},
"Finished {s} assets reading with {} blocks ({} migrations), {} items ({} migrations), {} tools, {} biomes ({} migrations), {} structure tables ({} migrations), {} recipes, {} structure building blocks, {} blueprints and {} particles",
.{@tagName(typ), self.blocks.count(), self.blockMigrations.count(), self.items.count(), self.itemMigrations.count(), self.tools.count(), self.biomes.count(), self.biomeMigrations.count(), self.structureTables.count(), self.structureTableMigrations.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()},
);
}

Expand Down Expand Up @@ -321,6 +331,7 @@ fn createAssetStringID(
}

pub fn init() void {
structures_zig.init();
biomes_zig.init();
blocks_zig.init();
migrations_zig.init();
Expand Down Expand Up @@ -370,6 +381,10 @@ fn registerBiome(numericId: u32, stringId: []const u8, zon: ZonElement) void {
biomes_zig.register(stringId, numericId, zon);
}

fn registerStructureTable(numericId: u32, stringId: []const u8, zon: ZonElement) void {
if(zon == .null) std.log.err("Missing StructureTable: {s}. Will not replace.", .{stringId});
structures_zig.register(stringId, numericId, zon);
}
fn registerRecipesFromZon(zon: ZonElement) void {
items_zig.registerRecipes(zon);
}
Expand Down Expand Up @@ -478,7 +493,7 @@ pub const Palette = struct { // MARK: Palette

var loadedAssets: bool = false;

pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPalette: *Palette, toolPalette: *Palette, biomePalette: *Palette) !void { // MARK: loadWorldAssets()
pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPalette: *Palette, toolPalette: *Palette, biomePalette: *Palette, structureTablePalette: *Palette) !void { // MARK: loadWorldAssets()
if(loadedAssets) return; // The assets already got loaded by the server.
loadedAssets = true;

Expand Down Expand Up @@ -608,6 +623,21 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale
while(iterator.next()) |entry| {
particles_zig.ParticleManager.register(assetFolder, entry.key_ptr.*, entry.value_ptr.*);
}
// StructureTables:

var nextStructureTableNumericId: u32 = 0;
for(structureTablePalette.palette.items) |id| {
registerStructureTable(nextStructureTableNumericId, id, worldAssets.structureTables.get(id) orelse .null);
nextStructureTableNumericId += 1;
}
iterator = worldAssets.structureTables.iterator();
while(iterator.next()) |entry| {
if(structures_zig.hasRegistered(entry.key_ptr.*)) continue;
registerStructureTable(nextStructureTableNumericId, entry.key_ptr.*, entry.value_ptr.*);
structureTablePalette.add(entry.key_ptr.*);
nextStructureTableNumericId += 1;
}
// TODO: structures_zig.finishloading(); -- Is this needed?

// Biomes:
var nextBiomeNumericId: u32 = 0;
Expand Down
4 changes: 3 additions & 1 deletion src/game.zig
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ pub const World = struct { // MARK: World
itemPalette: *assets.Palette = undefined,
toolPalette: *assets.Palette = undefined,
biomePalette: *assets.Palette = undefined,
structureTablePalette: *assets.Palette = undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the sorting, a palette isn't needed

itemDrops: ClientItemDropManager = undefined,
playerBiome: Atomic(*const main.server.terrain.biomes.Biome) = undefined,

Expand Down Expand Up @@ -668,6 +669,7 @@ pub const World = struct { // MARK: World

pub fn finishHandshake(self: *World, zon: ZonElement) !void {
// TODO: Consider using a per-world allocator.
self.structureTablePalette = try assets.Palette.init(main.globalAllocator, zon.getChild("structureTablePalette"), null);
self.blockPalette = try assets.Palette.init(main.globalAllocator, zon.getChild("blockPalette"), "cubyz:air");
errdefer self.blockPalette.deinit();
self.biomePalette = try assets.Palette.init(main.globalAllocator, zon.getChild("biomePalette"), null);
Expand All @@ -680,7 +682,7 @@ pub const World = struct { // MARK: World

const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/serverAssets", .{main.files.cubyzDirStr()}) catch unreachable;
defer main.stackAllocator.free(path);
try assets.loadWorldAssets(path, self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette);
try assets.loadWorldAssets(path, self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette, self.structureTablePalette);
Player.id = zon.get(u32, "player_id", std.math.maxInt(u32));
Player.inventory = Inventory.init(main.globalAllocator, Player.inventorySize, .normal, .{.playerInventory = Player.id}, .{});
Player.loadFrom(zon.getChild("player"));
Expand Down
180 changes: 38 additions & 142 deletions src/server/terrain/biomes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,10 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const vec = @import("main.vec");
const Vec3f = main.vec.Vec3f;
const Vec3d = main.vec.Vec3d;

pub const SimpleStructureModel = struct { // MARK: SimpleStructureModel
pub const GenerationMode = enum {
floor,
ceiling,
floor_and_ceiling,
air,
underground,
water_surface,
};
const VTable = struct {
loadModel: *const fn(arena: NeverFailingAllocator, parameters: ZonElement) *anyopaque,
generate: *const fn(self: *anyopaque, generationMode: GenerationMode, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void,
hashFunction: *const fn(self: *anyopaque) u64,
generationMode: GenerationMode,
};

vtable: VTable,
data: *anyopaque,
chance: f32,
priority: f32,
generationMode: GenerationMode,

pub fn initModel(parameters: ZonElement) ?SimpleStructureModel {
const id = parameters.get([]const u8, "id", "");
const vtable = modelRegistry.get(id) orelse {
std.log.err("Couldn't find structure model with id {s}", .{id});
return null;
};
return SimpleStructureModel{
.vtable = vtable,
.data = vtable.loadModel(arenaAllocator.allocator(), parameters),
.chance = parameters.get(f32, "chance", 0.1),
.priority = parameters.get(f32, "priority", 1),
.generationMode = std.meta.stringToEnum(GenerationMode, parameters.get([]const u8, "generationMode", "")) orelse vtable.generationMode,
};
}

pub fn generate(self: SimpleStructureModel, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void {
self.vtable.generate(self.data, self.generationMode, x, y, z, chunk, caveMap, biomeMap, seed, isCeiling);
}

var modelRegistry: std.StringHashMapUnmanaged(VTable) = .{};
var arenaAllocator: main.heap.NeverFailingArenaAllocator = .init(main.globalAllocator);

pub fn reset() void {
std.debug.assert(arenaAllocator.reset(.free_all));
}

pub fn registerGenerator(comptime Generator: type) void {
var self: VTable = undefined;
self.loadModel = main.utils.castFunctionReturnToAnyopaque(Generator.loadModel);
self.generate = main.utils.castFunctionSelfToAnyopaque(Generator.generate);
self.hashFunction = main.utils.castFunctionSelfToAnyopaque(struct {
fn hash(ptr: *Generator) u64 {
return hashGeneric(ptr.*);
}
}.hash);
self.generationMode = Generator.generationMode;
modelRegistry.put(main.globalAllocator.allocator, Generator.id, self) catch unreachable;
}

fn getHash(self: SimpleStructureModel) u64 {
return self.vtable.hashFunction(self.data);
}
};
const hash = main.utils.hash_zig;
const structures_zig = @import("structures.zig");
const SimpleStructureModel = structures_zig.SimpleStructureModel;
const StructureTable = structures_zig.StructureTable;

const Stripe = struct { // MARK: Stripe
direction: ?Vec3d,
Expand Down Expand Up @@ -140,80 +78,6 @@ const Stripe = struct { // MARK: Stripe
}
};

fn hashGeneric(input: anytype) u64 {
const T = @TypeOf(input);
return switch(@typeInfo(T)) {
.bool => hashCombine(hashInt(@intFromBool(input)), 0xbf58476d1ce4e5b9),
.@"enum" => hashCombine(hashInt(@as(u64, @intFromEnum(input))), 0x94d049bb133111eb),
.int, .float => blk: {
const value = @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(input));
break :blk hashInt(@as(u64, value));
},
.@"struct" => blk: {
if(@hasDecl(T, "getHash")) {
break :blk input.getHash();
}
var result: u64 = hashGeneric(@typeName(T));
inline for(@typeInfo(T).@"struct".fields) |field| {
const keyHash = hashGeneric(@as([]const u8, field.name));
const valueHash = hashGeneric(@field(input, field.name));
const keyValueHash = hashCombine(keyHash, valueHash);
result = hashCombine(result, keyValueHash);
}
break :blk result;
},
.optional => if(input) |_input| hashGeneric(_input) else 0,
.pointer => switch(@typeInfo(T).pointer.size) {
.one => blk: {
if(@typeInfo(@typeInfo(T).pointer.child) == .@"fn") break :blk 0;
if(@typeInfo(T).pointer.child == Biome) return hashGeneric(input.id);
if(@typeInfo(T).pointer.child == anyopaque) break :blk 0;
break :blk hashGeneric(input.*);
},
.slice => blk: {
var result: u64 = hashInt(input.len);
for(input) |val| {
const valueHash = hashGeneric(val);
result = hashCombine(result, valueHash);
}
break :blk result;
},
else => @compileError("Unsupported type " ++ @typeName(T)),
},
.array => blk: {
var result: u64 = 0xbf58476d1ce4e5b9;
for(input) |val| {
const valueHash = hashGeneric(val);
result = hashCombine(result, valueHash);
}
break :blk result;
},
.vector => blk: {
var result: u64 = 0x94d049bb133111eb;
inline for(0..@typeInfo(T).vector.len) |i| {
const valueHash = hashGeneric(input[i]);
result = hashCombine(result, valueHash);
}
break :blk result;
},
else => @compileError("Unsupported type " ++ @typeName(T)),
};
}

// https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes
fn hashCombine(left: u64, right: u64) u64 {
return left ^ (right +% 0x517cc1b727220a95 +% (left << 6) +% (left >> 2));
}

// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
fn hashInt(input: u64) u64 {
var x = input;
x = (x ^ (x >> 30))*%0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27))*%0x94d049bb133111eb;
x = x ^ (x >> 31);
return x;
}

pub const Interpolation = enum(u8) {
none,
linear,
Expand Down Expand Up @@ -315,10 +179,18 @@ pub const Biome = struct { // MARK: Biome
preferredMusic: []const u8, // TODO: Support multiple possibilities that are chosen based on time and danger.
isValidPlayerSpawn: bool,
chance: f32,
biomeTags: [][]const u8,

pub fn init(self: *Biome, id: []const u8, paletteId: u32, zon: ZonElement) void {
const minRadius = zon.get(f32, "radius", zon.get(f32, "minRadius", 256));
const maxRadius = zon.get(f32, "maxRadius", minRadius);
const biome_tags = zon.getChild("biomeTags");
var tags_list = main.ListUnmanaged([]const u8){};
if(biome_tags.toSlice().len > 0) {
for(biome_tags.toSlice()) |tag| {
tags_list.append(main.globalAllocator, tag.toString(main.globalAllocator));
}
}
self.* = Biome{
.id = main.globalAllocator.dupe(u8, id),
.paletteId = paletteId,
Expand Down Expand Up @@ -354,6 +226,7 @@ pub const Biome = struct { // MARK: Biome
.isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn", false),
.chance = zon.get(f32, "chance", if(zon == .null) 0 else 1),
.maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount", std.math.floatMax(f32)),
.biomeTags = tags_list.items,
};
if(self.minHeight > self.maxHeight) {
std.log.err("Biome {s} has invalid height range ({}, {})", .{self.id, self.minHeight, self.maxHeight});
Expand Down Expand Up @@ -389,12 +262,36 @@ pub const Biome = struct { // MARK: Biome
var vegetation = main.ListUnmanaged(SimpleStructureModel){};
var totalChance: f32 = 0;
defer vegetation.deinit(main.stackAllocator);
// Add structures from the biome's internal structure table
for(structures.toSlice()) |elem| {
if(SimpleStructureModel.initModel(elem)) |model| {
vegetation.append(main.stackAllocator, model);
totalChance += model.chance;
}
}
// Add structures from structure tables outside of the biome's internal table.
const structure_tables = main.server.terrain.structures.getSlice();
for(structure_tables) |table| {
std.log.debug("structure table biomeTags len: {}", .{table.biomeTags.len});
if(self.biomeTags.len > 0) {
std.log.debug("Biome tags len: {}", .{self.biomeTags.len});
for(self.biomeTags) |tag| {
for(table.biomeTags) |st_tag| {
if(std.mem.eql(u8, tag, st_tag)) {
for(table.structures) |model| {
vegetation.append(main.stackAllocator, model);
totalChance += model.chance;
}
}
}
}
} else if(table.biomeTags.len == 0) {
for(table.structures) |model| {
vegetation.append(main.stackAllocator, model);
totalChance += model.chance;
}
}
}
if(totalChance > 1) {
for(vegetation.items) |*model| {
model.chance /= totalChance;
Expand All @@ -420,7 +317,7 @@ pub const Biome = struct { // MARK: Biome
}

fn getCheckSum(self: *Biome) u64 {
return hashGeneric(self.*);
return hash.hashGeneric(self.*);
}
};

Expand Down Expand Up @@ -680,7 +577,6 @@ pub fn deinit() void {
biomesById.deinit();
biomesByIndex.deinit(main.globalAllocator);
// TODO? byTypeBiomes.deinit(main.globalAllocator);
SimpleStructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator);
}

pub fn register(id: []const u8, paletteId: u32, zon: ZonElement) void {
Expand Down
2 changes: 1 addition & 1 deletion src/server/terrain/simple_structures/Boulder.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement;
const terrain = main.server.terrain;
const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView;
const CaveMapView = terrain.CaveMap.CaveMapView;
const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode;
const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode;
const vec = main.vec;
const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
Expand Down
2 changes: 1 addition & 1 deletion src/server/terrain/simple_structures/FallenTree.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement;
const terrain = main.server.terrain;
const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView;
const CaveMapView = terrain.CaveMap.CaveMapView;
const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode;
const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode;
const vec = main.vec;
const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
Expand Down
Loading
Loading