Sort the world list in save selection by least recently used.

Also did some further refactoring to make it easier to deal with different world paths (#606) in the future.
fixes #1311
This commit is contained in:
IntegratedQuantum 2025-05-13 20:38:07 +02:00
parent ab4beca0f4
commit 8bcc00f536
8 changed files with 106 additions and 76 deletions

View File

@ -325,7 +325,7 @@ pub const Sync = struct { // MARK: Sync
defer main.stackAllocator.free(dest); defer main.stackAllocator.free(dest);
const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{main.server.world.?.name, hashedName}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{main.server.world.?.path, hashedName}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
const playerData = main.files.readToZon(main.stackAllocator, path) catch .null; const playerData = main.files.readToZon(main.stackAllocator, path) catch .null;

View File

@ -46,7 +46,8 @@ fn createWorld(_: usize) void {
fn flawedCreateWorld() !void { fn flawedCreateWorld() !void {
const worldName = textInput.currentString.items; const worldName = textInput.currentString.items;
const saveFolder = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldName}) catch unreachable; const worldPath = worldName; // TODO: Make sure that only valid file name characters are used, and add a check to allow different worlds of the same name.
const saveFolder = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldPath}) catch unreachable;
defer main.stackAllocator.free(saveFolder); defer main.stackAllocator.free(saveFolder);
if(std.fs.cwd().openDir(saveFolder, .{})) |_dir| { if(std.fs.cwd().openDir(saveFolder, .{})) |_dir| {
var dir = _dir; var dir = _dir;
@ -55,7 +56,7 @@ fn flawedCreateWorld() !void {
} else |_| {} } else |_| {}
try main.files.makeDir(saveFolder); try main.files.makeDir(saveFolder);
{ {
const generatorSettingsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{worldName}) catch unreachable; const generatorSettingsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{worldPath}) catch unreachable;
defer main.stackAllocator.free(generatorSettingsPath); defer main.stackAllocator.free(generatorSettingsPath);
const generatorSettings = main.ZonElement.initObject(main.stackAllocator); const generatorSettings = main.ZonElement.initObject(main.stackAllocator);
defer generatorSettings.deinit(main.stackAllocator); defer generatorSettings.deinit(main.stackAllocator);
@ -75,7 +76,19 @@ fn flawedCreateWorld() !void {
try main.files.writeZon(generatorSettingsPath, generatorSettings); try main.files.writeZon(generatorSettingsPath, generatorSettings);
} }
{ {
const gamerulePath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{worldName}) catch unreachable; const worldInfoPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.zig.zon", .{worldPath}) catch unreachable;
defer main.stackAllocator.free(worldInfoPath);
const worldInfo = main.ZonElement.initObject(main.stackAllocator);
defer worldInfo.deinit(main.stackAllocator);
worldInfo.put("name", worldName);
worldInfo.put("version", main.server.world_zig.worldDataVersion);
worldInfo.put("lastUsedTime", std.time.milliTimestamp());
try main.files.writeZon(worldInfoPath, worldInfo);
}
{
const gamerulePath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{worldPath}) catch unreachable;
defer main.stackAllocator.free(gamerulePath); defer main.stackAllocator.free(gamerulePath);
const gamerules = main.ZonElement.initObject(main.stackAllocator); const gamerules = main.ZonElement.initObject(main.stackAllocator);
defer gamerules.deinit(main.stackAllocator); defer gamerules.deinit(main.stackAllocator);
@ -86,7 +99,7 @@ fn flawedCreateWorld() !void {
try main.files.writeZon(gamerulePath, gamerules); try main.files.writeZon(gamerulePath, gamerules);
} }
{ // Make assets subfolder { // Make assets subfolder
const assetsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets", .{worldName}) catch unreachable; const assetsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets", .{worldPath}) catch unreachable;
defer main.stackAllocator.free(assetsPath); defer main.stackAllocator.free(assetsPath);
try main.files.makeDir(assetsPath); try main.files.makeDir(assetsPath);
} }

View File

@ -29,6 +29,13 @@ pub var needsUpdate: bool = false;
var deleteIcon: Texture = undefined; var deleteIcon: Texture = undefined;
var fileExplorerIcon: Texture = undefined; var fileExplorerIcon: Texture = undefined;
const WorldInfo = struct {
lastUsedTime: i64,
name: []const u8,
fileName: []const u8,
};
var worldList: main.ListUnmanaged(WorldInfo) = .{};
pub fn init() void { pub fn init() void {
deleteIcon = Texture.initFromFile("assets/cubyz/ui/delete_icon.png"); deleteIcon = Texture.initFromFile("assets/cubyz/ui/delete_icon.png");
fileExplorerIcon = Texture.initFromFile("assets/cubyz/ui/file_explorer_icon.png"); fileExplorerIcon = Texture.initFromFile("assets/cubyz/ui/file_explorer_icon.png");
@ -70,25 +77,18 @@ pub fn openWorld(name: []const u8) void {
gui.openHud(); gui.openHud();
} }
fn openWorldWrap(namePtr: usize) void { // TODO: Improve this situation. Maybe it makes sense to always use 2 arguments in the Callback. fn openWorldWrap(index: usize) void { // TODO: Improve this situation. Maybe it makes sense to always use 2 arguments in the Callback.
const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr); openWorld(worldList.items[index].fileName);
const name = std.mem.span(nullTerminatedName);
openWorld(name);
} }
fn deleteWorld(namePtr: usize) void { fn deleteWorld(index: usize) void {
const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr);
const name = std.mem.span(nullTerminatedName);
main.gui.closeWindow("delete_world_confirmation"); main.gui.closeWindow("delete_world_confirmation");
main.gui.windowlist.delete_world_confirmation.setDeleteWorldName(name); main.gui.windowlist.delete_world_confirmation.setDeleteWorldName(worldList.items[index].fileName);
main.gui.openWindow("delete_world_confirmation"); main.gui.openWindow("delete_world_confirmation");
} }
fn openFolder(namePtr: usize) void { fn openFolder(index: usize) void {
const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr); const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldList.items[index].fileName}) catch unreachable;
const name = std.mem.span(nullTerminatedName);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{name}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
main.files.openDirInWindow(path); main.files.openDirInWindow(path);
@ -149,22 +149,41 @@ pub fn onOpen() void {
break :readingSaves; break :readingSaves;
}) |entry| { }) |entry| {
if(entry.kind == .directory) { if(entry.kind == .directory) {
const row = HorizontalList.init(); const worldInfoPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.zig.zon", .{entry.name}) catch unreachable;
defer main.stackAllocator.free(worldInfoPath);
const worldInfo = main.files.readToZon(main.stackAllocator, worldInfoPath) catch |err| {
std.log.err("Couldn't open save {s}: {s}", .{worldInfoPath, @errorName(err)});
continue;
};
defer worldInfo.deinit(main.stackAllocator);
const decodedName = parseEscapedFolderName(main.stackAllocator, entry.name); const decodedName = parseEscapedFolderName(main.stackAllocator, entry.name);
defer main.stackAllocator.free(decodedName); defer main.stackAllocator.free(decodedName);
const name = buttonNameArena.allocator().dupeZ(u8, entry.name); // Null terminate, so we can later recover the string from just the pointer.
const buttonName = std.fmt.allocPrint(buttonNameArena.allocator().allocator, "{s}", .{decodedName}) catch unreachable;
row.add(Button.initText(.{0, 0}, 128, buttonName, .{.callback = &openWorldWrap, .arg = @intFromPtr(name.ptr)})); worldList.append(main.globalAllocator, .{
row.add(Button.initIcon(.{8, 0}, .{16, 16}, fileExplorerIcon, false, .{.callback = &openFolder, .arg = @intFromPtr(name.ptr)})); .fileName = main.globalAllocator.dupe(u8, entry.name),
row.add(Button.initIcon(.{8, 0}, .{16, 16}, deleteIcon, false, .{.callback = &deleteWorld, .arg = @intFromPtr(name.ptr)})); .lastUsedTime = worldInfo.get(i64, "lastUsedTime", 0),
row.finish(.{0, 0}, .center); .name = main.globalAllocator.dupe(u8, worldInfo.get([]const u8, "", decodedName)),
list.add(row); });
} }
} }
} }
std.sort.insertion(WorldInfo, worldList.items, {}, struct {
fn lessThan(_: void, lhs: WorldInfo, rhs: WorldInfo)bool {
return rhs.lastUsedTime -% lhs.lastUsedTime < 0;
}
}.lessThan);
for(worldList.items, 0..) |worldInfo, i| {
const row = HorizontalList.init();
row.add(Button.initText(.{0, 0}, 128, worldInfo.name, .{.callback = &openWorldWrap, .arg = i}));
row.add(Button.initIcon(.{8, 0}, .{16, 16}, fileExplorerIcon, false, .{.callback = &openFolder, .arg = i}));
row.add(Button.initIcon(.{8, 0}, .{16, 16}, deleteIcon, false, .{.callback = &deleteWorld, .arg = i}));
row.finish(.{0, 0}, .center);
list.add(row);
}
list.finish(.center); list.finish(.center);
window.rootComponent = list.toComponent(); window.rootComponent = list.toComponent();
window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding)); window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding));
@ -172,6 +191,11 @@ pub fn onOpen() void {
} }
pub fn onClose() void { pub fn onClose() void {
for(worldList.items) |worldInfo| {
main.globalAllocator.free(worldInfo.fileName);
main.globalAllocator.free(worldInfo.name);
}
worldList.clearAndFree(main.globalAllocator);
buttonNameArena.deinit(); buttonNameArena.deinit();
if(window.rootComponent) |*comp| { if(window.rootComponent) |*comp| {
comp.deinit(); comp.deinit();

View File

@ -667,7 +667,7 @@ pub const Protocols = struct {
std.log.info("User {s} joined using version {s}.", .{name, version}); std.log.info("User {s} joined using version {s}.", .{name, version});
{ {
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets/", .{main.server.world.?.name}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets/", .{main.server.world.?.path}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
var dir = try std.fs.cwd().openDir(path, .{.iterate = true}); var dir = try std.fs.cwd().openDir(path, .{.iterate = true});
defer dir.close(); defer dir.close();

View File

@ -17,7 +17,8 @@ const Blueprint = main.blueprint.Blueprint;
const NeverFailingAllocator = main.heap.NeverFailingAllocator; const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const CircularBufferQueue = main.utils.CircularBufferQueue; const CircularBufferQueue = main.utils.CircularBufferQueue;
pub const ServerWorld = @import("world.zig").ServerWorld; pub const world_zig = @import("world.zig");
pub const ServerWorld = world_zig.ServerWorld;
pub const terrain = @import("terrain/terrain.zig"); pub const terrain = @import("terrain/terrain.zig");
pub const Entity = @import("Entity.zig"); pub const Entity = @import("Entity.zig");
pub const storage = @import("storage.zig"); pub const storage = @import("storage.zig");

View File

@ -215,7 +215,7 @@ fn cacheInit(pos: chunk.ChunkPosition) *RegionFile {
return region; return region;
} }
hashMapMutex.unlock(); hashMapMutex.unlock();
const path: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{server.world.?.name}) catch unreachable; const path: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{server.world.?.path}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
return RegionFile.init(pos, path); return RegionFile.init(pos, path);
} }

View File

@ -125,7 +125,7 @@ pub const MapFragment = struct { // MARK: MapFragment
}; };
pub fn load(self: *MapFragment, biomePalette: *main.assets.Palette, originalHeightMap: ?*[mapSize][mapSize]i32) !NeighborInfo { pub fn load(self: *MapFragment, biomePalette: *main.assets.Palette, originalHeightMap: ?*[mapSize][mapSize]i32) !NeighborInfo {
const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable; const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.path}) catch unreachable;
defer main.stackAllocator.free(saveFolder); defer main.stackAllocator.free(saveFolder);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable;
@ -208,7 +208,7 @@ pub const MapFragment = struct { // MARK: MapFragment
outputWriter.writeInt(u8, @bitCast(header.neighborInfo)); outputWriter.writeInt(u8, @bitCast(header.neighborInfo));
outputWriter.writeSlice(compressedData); outputWriter.writeSlice(compressedData);
const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable; const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.path}) catch unreachable;
defer main.stackAllocator.free(saveFolder); defer main.stackAllocator.free(saveFolder);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable;

View File

@ -355,8 +355,9 @@ const ChunkManager = struct { // MARK: ChunkManager
} }
}; };
pub const worldDataVersion: u32 = 2;
const WorldIO = struct { // MARK: WorldIO const WorldIO = struct { // MARK: WorldIO
const worldDataVersion: u32 = 2;
dir: files.Dir, dir: files.Dir,
world: *ServerWorld, world: *ServerWorld,
@ -372,10 +373,6 @@ const WorldIO = struct { // MARK: WorldIO
self.dir.close(); self.dir.close();
} }
pub fn hasWorldData(self: WorldIO) bool {
return self.dir.hasFile("world.zig.zon");
}
/// Load the seed, which is needed before custom item and ore generation. /// Load the seed, which is needed before custom item and ore generation.
pub fn loadWorldSeed(self: WorldIO) !u64 { pub fn loadWorldSeed(self: WorldIO) !u64 {
const worldData = try self.dir.readToZon(main.stackAllocator, "world.zig.zon"); const worldData = try self.dir.readToZon(main.stackAllocator, "world.zig.zon");
@ -384,7 +381,7 @@ const WorldIO = struct { // MARK: WorldIO
std.log.err("Cannot read world file version {}. Expected version {}.", .{worldData.get(u32, "version", 0), worldDataVersion}); std.log.err("Cannot read world file version {}. Expected version {}.", .{worldData.get(u32, "version", 0), worldDataVersion});
return error.OldWorld; return error.OldWorld;
} }
return worldData.get(u64, "seed", 0); return worldData.get(?u64, "seed", null) orelse main.random.nextInt(u48, &main.seed);
} }
pub fn loadWorldData(self: WorldIO) !void { pub fn loadWorldData(self: WorldIO) !void {
@ -395,6 +392,7 @@ const WorldIO = struct { // MARK: WorldIO
self.world.gameTime = worldData.get(i64, "gameTime", 0); self.world.gameTime = worldData.get(i64, "gameTime", 0);
self.world.spawn = worldData.get(Vec3i, "spawn", .{0, 0, 0}); self.world.spawn = worldData.get(Vec3i, "spawn", .{0, 0, 0});
self.world.biomeChecksum = worldData.get(i64, "biomeChecksum", 0); self.world.biomeChecksum = worldData.get(i64, "biomeChecksum", 0);
self.world.name = main.globalAllocator.dupe(u8, worldData.get([]const u8, "name", self.world.path));
} }
pub fn saveWorldData(self: WorldIO) !void { pub fn saveWorldData(self: WorldIO) !void {
@ -406,6 +404,8 @@ const WorldIO = struct { // MARK: WorldIO
worldData.put("gameTime", self.world.gameTime); worldData.put("gameTime", self.world.gameTime);
worldData.put("spawn", self.world.spawn); worldData.put("spawn", self.world.spawn);
worldData.put("biomeChecksum", self.world.biomeChecksum); worldData.put("biomeChecksum", self.world.biomeChecksum);
worldData.put("name", self.world.name);
worldData.put("lastUsedTime", std.time.milliTimestamp());
// TODO: Save entities // TODO: Save entities
try self.dir.writeZon("world.zig.zon", worldData); try self.dir.writeZon("world.zig.zon", worldData);
} }
@ -420,8 +420,6 @@ pub const ServerWorld = struct { // MARK: ServerWorld
biomePalette: *main.assets.Palette = undefined, biomePalette: *main.assets.Palette = undefined,
chunkManager: ChunkManager = undefined, chunkManager: ChunkManager = undefined,
generated: bool = false,
gameTime: i64 = 0, gameTime: i64 = 0,
milliTime: i64, milliTime: i64,
lastUpdateTime: i64, lastUpdateTime: i64,
@ -432,7 +430,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld
allowCheats: bool = undefined, allowCheats: bool = undefined,
seed: u64, seed: u64,
name: []const u8, path: []const u8,
name: []const u8 = &.{},
spawn: Vec3i = undefined, spawn: Vec3i = undefined,
wio: WorldIO = undefined, wio: WorldIO = undefined,
@ -454,14 +453,14 @@ pub const ServerWorld = struct { // MARK: ServerWorld
milliTimeStamp: i64, milliTimeStamp: i64,
}; };
pub fn init(name: []const u8, nullGeneratorSettings: ?ZonElement) !*ServerWorld { // MARK: init() pub fn init(path: []const u8, nullGeneratorSettings: ?ZonElement) !*ServerWorld { // MARK: init()
covert_old_worlds: { // TODO: Remove after #480 covert_old_worlds: { // TODO: Remove after #480
const worldDatPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.dat", .{name}); const worldDatPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.dat", .{path});
defer main.stackAllocator.free(worldDatPath); defer main.stackAllocator.free(worldDatPath);
if(std.fs.cwd().openFile(worldDatPath, .{})) |file| { if(std.fs.cwd().openFile(worldDatPath, .{})) |file| {
file.close(); file.close();
std.log.warn("Detected old world in saves/{s}. Converting all .json files to .zig.zon", .{name}); std.log.warn("Detected old world in saves/{s}. Converting all .json files to .zig.zon", .{path});
const dirPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{name}); const dirPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{path});
defer main.stackAllocator.free(dirPath); defer main.stackAllocator.free(dirPath);
var dir = std.fs.cwd().openDir(dirPath, .{.iterate = true}) catch |err| { var dir = std.fs.cwd().openDir(dirPath, .{.iterate = true}) catch |err| {
std.log.err("Could not open world directory to convert json files: {s}. Conversion aborted", .{@errorName(err)}); std.log.err("Could not open world directory to convert json files: {s}. Conversion aborted", .{@errorName(err)});
@ -490,7 +489,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
.milliTime = std.time.milliTimestamp(), .milliTime = std.time.milliTimestamp(),
.lastUnimportantDataSent = std.time.milliTimestamp(), .lastUnimportantDataSent = std.time.milliTimestamp(),
.seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))), .seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))),
.name = main.globalAllocator.dupe(u8, name), .path = main.globalAllocator.dupe(u8, path),
.chunkUpdateQueue = .init(main.globalAllocator, 256), .chunkUpdateQueue = .init(main.globalAllocator, 256),
.regionUpdateQueue = .init(main.globalAllocator, 256), .regionUpdateQueue = .init(main.globalAllocator, 256),
}; };
@ -505,45 +504,38 @@ pub const ServerWorld = struct { // MARK: ServerWorld
if(nullGeneratorSettings) |_generatorSettings| { if(nullGeneratorSettings) |_generatorSettings| {
generatorSettings = _generatorSettings; generatorSettings = _generatorSettings;
// Store generator settings: // Store generator settings:
try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{name}), generatorSettings); try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{path}), generatorSettings);
} else { // Read the generator settings: } else { // Read the generator settings:
generatorSettings = try files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{name})); generatorSettings = try files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{path}));
} }
self.wio = WorldIO.init(try files.openDir(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}", .{name})), self); self.wio = WorldIO.init(try files.openDir(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}", .{path})), self);
errdefer self.wio.deinit(); errdefer self.wio.deinit();
const blockPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{name})) catch .null; const blockPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{path})) catch .null;
self.blockPalette = try main.assets.Palette.init(main.globalAllocator, blockPaletteZon, "cubyz:air"); self.blockPalette = try main.assets.Palette.init(main.globalAllocator, blockPaletteZon, "cubyz:air");
errdefer self.blockPalette.deinit(); errdefer self.blockPalette.deinit();
std.log.info("Loaded save block palette with {} blocks.", .{self.blockPalette.size()}); std.log.info("Loaded save block palette with {} blocks.", .{self.blockPalette.size()});
const itemPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{name})) catch .null; const itemPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{path})) catch .null;
self.itemPalette = try main.assets.Palette.init(main.globalAllocator, itemPaletteZon, null); self.itemPalette = try main.assets.Palette.init(main.globalAllocator, itemPaletteZon, null);
errdefer self.itemPalette.deinit(); errdefer self.itemPalette.deinit();
std.log.info("Loaded save item palette with {} items.", .{self.itemPalette.size()}); std.log.info("Loaded save item palette with {} items.", .{self.itemPalette.size()});
const biomePaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{name})) catch .null; const biomePaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{path})) catch .null;
self.biomePalette = try main.assets.Palette.init(main.globalAllocator, biomePaletteZon, null); self.biomePalette = try main.assets.Palette.init(main.globalAllocator, biomePaletteZon, null);
errdefer self.biomePalette.deinit(); errdefer self.biomePalette.deinit();
std.log.info("Loaded save biome palette with {} biomes.", .{self.biomePalette.size()}); std.log.info("Loaded save biome palette with {} biomes.", .{self.biomePalette.size()});
errdefer main.assets.unloadAssets(); errdefer main.assets.unloadAssets();
if(self.wio.hasWorldData()) { self.seed = try self.wio.loadWorldSeed();
self.seed = try self.wio.loadWorldSeed(); try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{path}), self.blockPalette, self.itemPalette, self.biomePalette);
self.generated = true;
try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{name}), self.blockPalette, self.itemPalette, self.biomePalette);
} else {
self.seed = main.random.nextInt(u48, &main.seed);
try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{name}), self.blockPalette, self.itemPalette, self.biomePalette);
try self.wio.saveWorldData();
}
// Store the block palette now that everything is loaded. // Store the block palette now that everything is loaded.
try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{name}), self.blockPalette.storeToZon(arenaAllocator)); try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{path}), self.blockPalette.storeToZon(arenaAllocator));
try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{name}), self.biomePalette.storeToZon(arenaAllocator)); try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{path}), self.biomePalette.storeToZon(arenaAllocator));
try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{name}), self.itemPalette.storeToZon(arenaAllocator)); try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{path}), self.itemPalette.storeToZon(arenaAllocator));
var gamerules = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{name})) catch ZonElement.initObject(arenaAllocator); var gamerules = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{path})) catch ZonElement.initObject(arenaAllocator);
self.defaultGamemode = std.meta.stringToEnum(main.game.Gamemode, gamerules.get([]const u8, "default_gamemode", "creative")) orelse .creative; self.defaultGamemode = std.meta.stringToEnum(main.game.Gamemode, gamerules.get([]const u8, "default_gamemode", "creative")) orelse .creative;
self.allowCheats = gamerules.get(bool, "cheats", true); self.allowCheats = gamerules.get(bool, "cheats", true);
@ -573,7 +565,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld
self.itemPalette.deinit(); self.itemPalette.deinit();
self.biomePalette.deinit(); self.biomePalette.deinit();
self.wio.deinit(); self.wio.deinit();
main.globalAllocator.free(self.name); main.globalAllocator.free(self.path);
//main.globalAllocator.free(self.name);
main.globalAllocator.destroy(self); main.globalAllocator.destroy(self);
} }
@ -666,19 +659,19 @@ pub const ServerWorld = struct { // MARK: ServerWorld
fn regenerateLOD(self: *ServerWorld, newBiomeCheckSum: i64) !void { fn regenerateLOD(self: *ServerWorld, newBiomeCheckSum: i64) !void {
std.log.info("Biomes have changed. Regenerating LODs... (this might take some time)", .{}); std.log.info("Biomes have changed. Regenerating LODs... (this might take some time)", .{});
const hasSurfaceMaps = blk: { const hasSurfaceMaps = blk: {
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{self.name}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{self.path}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
var dir = std.fs.cwd().openDir(path, .{}) catch break :blk false; var dir = std.fs.cwd().openDir(path, .{}) catch break :blk false;
defer dir.close(); defer dir.close();
break :blk true; break :blk true;
}; };
if(hasSurfaceMaps) { if(hasSurfaceMaps) {
try terrain.SurfaceMap.regenerateLOD(self.name); try terrain.SurfaceMap.regenerateLOD(self.path);
} }
// Delete old LODs: // Delete old LODs:
for(1..main.settings.highestSupportedLod + 1) |i| { for(1..main.settings.highestSupportedLod + 1) |i| {
const lod = @as(u32, 1) << @intCast(i); const lod = @as(u32, 1) << @intCast(i);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{self.name}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{self.path}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
const dir = std.fmt.allocPrint(main.stackAllocator.allocator, "{}", .{lod}) catch unreachable; const dir = std.fmt.allocPrint(main.stackAllocator.allocator, "{}", .{lod}) catch unreachable;
defer main.stackAllocator.free(dir); defer main.stackAllocator.free(dir);
@ -691,7 +684,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
// Find all the stored chunks: // Find all the stored chunks:
var chunkPositions = main.List(ChunkPosition).init(main.stackAllocator); var chunkPositions = main.List(ChunkPosition).init(main.stackAllocator);
defer chunkPositions.deinit(); defer chunkPositions.deinit();
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks/1", .{self.name}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks/1", .{self.path}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
blk: { blk: {
var dirX = std.fs.cwd().openDir(path, .{.iterate = true}) catch |err| { var dirX = std.fs.cwd().openDir(path, .{.iterate = true}) catch |err| {
@ -754,7 +747,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
pub fn generate(self: *ServerWorld) !void { pub fn generate(self: *ServerWorld) !void {
try self.wio.loadWorldData(); // load data here in order for entities to also be loaded. try self.wio.loadWorldData(); // load data here in order for entities to also be loaded.
if(!self.generated) { if(@reduce(.And, self.spawn == Vec3i{0, 0, 0})) {
var seed: u64 = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))); var seed: u64 = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp())));
std.log.info("Finding position..", .{}); std.log.info("Finding position..", .{});
foundPosition: { foundPosition: {
@ -812,7 +805,6 @@ pub const ServerWorld = struct { // MARK: ServerWorld
defer map.decreaseRefCount(); defer map.decreaseRefCount();
self.spawn[2] = map.getHeight(self.spawn[0], self.spawn[1]) + 1; self.spawn[2] = map.getHeight(self.spawn[0], self.spawn[1]) + 1;
} }
self.generated = true;
const newBiomeCheckSum: i64 = @bitCast(terrain.biomes.getBiomeCheckSum(self.seed)); const newBiomeCheckSum: i64 = @bitCast(terrain.biomes.getBiomeCheckSum(self.seed));
if(newBiomeCheckSum != self.biomeChecksum) { if(newBiomeCheckSum != self.biomeChecksum) {
self.regenerateLOD(newBiomeCheckSum) catch |err| { self.regenerateLOD(newBiomeCheckSum) catch |err| {
@ -820,7 +812,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
}; };
} }
try self.wio.saveWorldData(); try self.wio.saveWorldData();
const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.name}) catch unreachable; const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.path}) catch unreachable;
defer main.stackAllocator.free(itemsPath); defer main.stackAllocator.free(itemsPath);
const zon = files.readToZon(main.stackAllocator, itemsPath) catch .null; const zon = files.readToZon(main.stackAllocator, itemsPath) catch .null;
defer zon.deinit(main.stackAllocator); defer zon.deinit(main.stackAllocator);
@ -832,7 +824,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
defer main.stackAllocator.free(dest); defer main.stackAllocator.free(dest);
const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.name, hashedName}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.path, hashedName}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
const playerData = files.readToZon(main.stackAllocator, path) catch .null; const playerData = files.readToZon(main.stackAllocator, path) catch .null;
@ -854,7 +846,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
defer main.stackAllocator.free(dest); defer main.stackAllocator.free(dest);
const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name);
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.name, hashedName}) catch unreachable; const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.path, hashedName}) catch unreachable;
defer main.stackAllocator.free(path); defer main.stackAllocator.free(path);
var playerZon: ZonElement = files.readToZon(main.stackAllocator, path) catch .null; var playerZon: ZonElement = files.readToZon(main.stackAllocator, path) catch .null;
@ -882,7 +874,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
} }
} }
const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.name}) catch unreachable; const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.path}) catch unreachable;
defer main.stackAllocator.free(playerPath); defer main.stackAllocator.free(playerPath);
try files.makeDir(playerPath); try files.makeDir(playerPath);
@ -907,7 +899,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld
const itemDropZon = self.itemDropManager.store(main.stackAllocator); const itemDropZon = self.itemDropManager.store(main.stackAllocator);
defer itemDropZon.deinit(main.stackAllocator); defer itemDropZon.deinit(main.stackAllocator);
const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.name}) catch unreachable; const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.path}) catch unreachable;
defer main.stackAllocator.free(itemsPath); defer main.stackAllocator.free(itemsPath);
try files.writeZon(itemsPath, itemDropZon); try files.writeZon(itemsPath, itemDropZon);
} }