diff --git a/src/assets.zig b/src/assets.zig index ea2e243f..249f1790 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -190,18 +190,20 @@ pub const BlockPalette = struct { } pub fn save(self: *BlockPalette, allocator: Allocator) !JsonElement { - var json = JsonElement{ - .JsonObject = std.StringHashMap(JsonElement).init(allocator), - }; + const json = try JsonElement.initObject(allocator); errdefer json.free(allocator); for(self.palette.items, 0..) |item, i| { - json.JsonObject.put(try allocator.dupe(u8, item), JsonElement{.JsonInt = @intCast(i64, i)}); + try json.put(try allocator.dupe(u8, item), i); } return json; } }; +var loadedAssets: bool = false; + pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void { + if(loadedAssets) return; // The assets already got loaded by the server. + loadedAssets = true; var blocks = try commonBlocks.cloneWithAllocator(main.threadAllocator); defer blocks.clearAndFree(); var items = try commonItems.cloneWithAllocator(main.threadAllocator); @@ -265,6 +267,13 @@ pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void { // } } +pub fn unloadAssets() void { + if(!loadedAssets) return; + loadedAssets = false; + blocks_zig.reset(); + items_zig.reset(); +} + pub fn deinit() void { arena.deinit(); } diff --git a/src/blocks.zig b/src/blocks.zig index 2d37a5b4..8bcd9113 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -130,7 +130,7 @@ pub fn reset() void { // TODO: Use arena.reset() instead. arena.deinit(); arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - reverseIndices = std.StringHashMap([]const u8).init(arena); + reverseIndices = std.StringHashMap(u16).init(arena.allocator()); } pub fn getByID(id: []const u8) u16 { diff --git a/src/chunk.zig b/src/chunk.zig index b37c552d..58b07f30 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -77,11 +77,19 @@ pub const ChunkPosition = struct { wz: i32, voxelSize: u31, -// TODO(mabye?): -// public int hashCode() { -// int shift = Math.min(Integer.numberOfTrailingZeros(wx), Math.min(Integer.numberOfTrailingZeros(wy), Integer.numberOfTrailingZeros(wz))); -// return (((wx >> shift) * 31 + (wy >> shift)) * 31 + (wz >> shift)) * 31 + voxelSize; -// } + pub fn hashCode(self: ChunkPosition) u32 { + const shift = @truncate(u5, @min(@ctz(self.wx), @min(@ctz(self.wy), @ctz(self.wz)))); + return (((@bitCast(u32, self.wx) >> shift) *% 31 +% (@bitCast(u32, self.wy) >> shift)) *% 31 +% (@bitCast(u32, self.wz) >> shift)) *% 31 +% self.voxelSize; + } + + pub fn equals(self: ChunkPosition, other: anytype) bool { + if(@TypeOf(other) == ?*Chunk) { + if(other) |ch| { + return self.wx == ch.pos.wx and self.wy == ch.pos.wy and self.wz == ch.pos.wz and self.voxelSize == ch.pos.voxelSize; + } + return false; + } else @compileError("Unsupported"); + } pub fn getMinDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 { var halfWidth = @intToFloat(f64, self.voxelSize*@divExact(chunkSize, 2)); diff --git a/src/files.zig b/src/files.zig index 698f3109..f13b370d 100644 --- a/src/files.zig +++ b/src/files.zig @@ -5,25 +5,68 @@ const main = @import("root"); const JsonElement = main.JsonElement; pub fn read(allocator: Allocator, path: []const u8) ![]u8 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - return try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + return cwd().read(allocator, path); } pub fn readToJson(allocator: Allocator, path: []const u8) !JsonElement { - const string = try read(main.threadAllocator, path); - defer main.threadAllocator.free(string); - return JsonElement.parseFromString(allocator, string); + return cwd().readToJson(allocator, path); } pub fn write(path: []const u8, data: []const u8) !void { - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); - try file.writeAll(data); + try cwd().write(path, data); } pub fn writeJson(path: []const u8, json: JsonElement) !void { - const string = try json.toString(main.threadAllocator); - defer main.threadAllocator.free(string); - try write(path, string); -} \ No newline at end of file + try cwd().writeJson(path, json); +} + +pub fn openDir(path: []const u8) !Dir { + var dir = try std.fs.cwd().makeOpenPath(path, .{}); + return Dir { + .dir = dir, + }; +} + +fn cwd() Dir { + return Dir { + .dir = std.fs.cwd(), + }; +} + +pub const Dir = struct { + dir: std.fs.Dir, + + pub fn close(self: *Dir) void { + self.dir.close(); + } + + pub fn read(self: Dir, allocator: Allocator, path: []const u8) ![]u8 { + const file = try self.dir.openFile(path, .{}); + defer file.close(); + return try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + } + + pub fn readToJson(self: Dir, allocator: Allocator, path: []const u8) !JsonElement { + const string = try self.read(main.threadAllocator, path); + defer main.threadAllocator.free(string); + return JsonElement.parseFromString(allocator, string); + } + + pub fn write(self: Dir, path: []const u8, data: []const u8) !void { + const file = try self.dir.createFile(path, .{}); + defer file.close(); + try file.writeAll(data); + } + + pub fn writeJson(self: Dir, path: []const u8, json: JsonElement) !void { + const string = try json.toString(main.threadAllocator); + defer main.threadAllocator.free(string); + try self.write(path, string); + } + + pub fn hasFile(self: Dir, path: []const u8) bool { + const file = self.dir.openFile(path, .{}) catch return false; + file.close(); + return true; + } +}; \ No newline at end of file diff --git a/src/game.zig b/src/game.zig index bc153732..3819b759 100644 --- a/src/game.zig +++ b/src/game.zig @@ -122,6 +122,13 @@ pub const World = struct { self.blockPalette.deinit(); Player.inventory__SEND_CHANGES_TO_SERVER.deinit(main.globalAllocator); self.manager.deinit(); + assets.unloadAssets(); + main.server.stop(); + main.threadPool.clear(); + if(main.server.thread) |serverThread| { + serverThread.join(); + main.server.thread = null; + } } pub fn finishHandshake(self: *World, json: JsonElement) !void { diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig index e2c42aa0..2d667354 100644 --- a/src/gui/windows/save_selection.zig +++ b/src/gui/windows/save_selection.zig @@ -28,8 +28,9 @@ fn openWorld(namePtr: usize) void { const nullTerminatedName = @intToPtr([*:0]const u8, namePtr); const name = std.mem.span(nullTerminatedName); std.log.info("TODO: Open world {s}", .{name}); - _ = std.Thread.spawn(.{}, main.server.start, .{}) catch |err| { + main.server.thread = std.Thread.spawn(.{}, main.server.start, .{name}) catch |err| { std.log.err("Encountered error while starting server thread: {s}", .{@errorName(err)}); + return; }; const connection = ConnectionManager.init(main.settings.defaultPort+1, false) catch |err| { diff --git a/src/itemdrop.zig b/src/itemdrop.zig index bb535dba..28a5f766 100644 --- a/src/itemdrop.zig +++ b/src/itemdrop.zig @@ -6,6 +6,7 @@ const chunk_zig = @import("chunk.zig"); const Chunk = chunk_zig.Chunk; const game = @import("game.zig"); const World = game.World; +const ServerWorld = main.server.ServerWorld; const graphics = @import("graphics.zig"); const c = graphics.c; const items = @import("items.zig"); @@ -26,8 +27,8 @@ const ItemDrop = struct { vel: Vec3d, rot: Vec3f, itemStack: ItemStack, - despawnTime: u32, - pickupCooldown: u32, + despawnTime: i32, + pickupCooldown: i32, reverseIndex: u16, }; @@ -54,7 +55,7 @@ pub const ItemDropManager = struct { isEmpty: std.bit_set.ArrayBitSet(usize, maxCapacity), - world: *World, + world: ?*ServerWorld, gravity: f64, airDragFactor: f64, @@ -63,17 +64,17 @@ pub const ItemDropManager = struct { lastUpdates: JsonElement, // TODO: Get rid of this inheritance pattern. - addWithIndexAndRotation: *const fn(*ItemDropManager, u16, Vec3d, Vec3d, Vec3f, ItemStack, u32, u32) void, + addWithIndexAndRotation: *const fn(*ItemDropManager, u16, Vec3d, Vec3d, Vec3f, ItemStack, i32, i32) void, - pub fn init(self: *ItemDropManager, allocator: Allocator, world: *World) !void { + pub fn init(self: *ItemDropManager, allocator: Allocator, world: ?*ServerWorld, gravity: f64) !void { self.* = ItemDropManager { .allocator = allocator, .list = std.MultiArrayList(ItemDrop){}, .lastUpdates = try JsonElement.initArray(allocator), .isEmpty = std.bit_set.ArrayBitSet(usize, maxCapacity).initFull(), .world = world, - .gravity = world.gravity, - .airDragFactor = world.gravity/maxSpeed, + .gravity = gravity, + .airDragFactor = gravity/maxSpeed, .addWithIndexAndRotation = &defaultAddWithIndexAndRotation, }; try self.list.resize(self.allocator, maxCapacity); @@ -110,7 +111,7 @@ pub const ItemDropManager = struct { json.get(f64, "vz", 0), }, items.ItemStack{.item = item, .amount = json.get(u16, "amount", 1)}, - json.get(u32, "despawnTime", 60), + json.get(i32, "despawnTime", 60), 0 }; if(json.get(?usize, "i", null)) |i| { @@ -168,6 +169,7 @@ pub const ItemDropManager = struct { } pub fn update(self: *ItemDropManager, deltaTime: f32) void { + std.debug.assert(self.world != null); const pos = self.list.items(.pos); const vel = self.list.items(.vel); const pickupCooldown = self.list.items(.pickupCooldown); @@ -175,7 +177,7 @@ pub const ItemDropManager = struct { var ii: u32 = 0; while(ii < self.size) : (ii += 1) { const i = self.indices[ii]; - if(self.world.getChunk(pos[i][0], pos[i][1], pos[i][2])) |chunk| { + if(self.world.?.getChunk(@floatToInt(i32, pos[i][0]), @floatToInt(i32, pos[i][1]), @floatToInt(i32, pos[i][2]))) |chunk| { // Check collision with blocks: self.updateEnt(chunk, &pos[i], &vel[i], deltaTime); } @@ -220,7 +222,7 @@ pub const ItemDropManager = struct { // } // } - pub fn addFromBlockPosition(self: *ItemDropManager, blockPos: Vec3i, vel: Vec3d, itemStack: ItemStack, despawnTime: u32) void { + pub fn addFromBlockPosition(self: *ItemDropManager, blockPos: Vec3i, vel: Vec3d, itemStack: ItemStack, despawnTime: i32) void { self.add( Vec3d { @intToFloat(f64, blockPos[0]) + @floatCast(f64, random.nextFloat(&main.seed)), // TODO: Consider block bounding boxes. @@ -237,7 +239,7 @@ pub const ItemDropManager = struct { ); } - pub fn add(self: *ItemDropManager, pos: Vec3d, vel: Vec3d, itemStack: ItemStack, despawnTime: u32, pickupCooldown: u32) !void { + pub fn add(self: *ItemDropManager, pos: Vec3d, vel: Vec3d, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) !void { try self.addWithRotation( pos, vel, Vec3f { @@ -249,7 +251,7 @@ pub const ItemDropManager = struct { ); } - pub fn addWithIndex(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, itemStack: ItemStack, despawnTime: u32, pickupCooldown: u32) void { + pub fn addWithIndex(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { self.addWithIndexAndRotation( self, i, pos, vel, Vec3f { @@ -261,7 +263,7 @@ pub const ItemDropManager = struct { ); } - pub fn addWithRotation(self: *ItemDropManager, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: u32, pickupCooldown: u32) !void { + pub fn addWithRotation(self: *ItemDropManager, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) !void { var i: u16 = undefined; { self.mutex.lock(); @@ -282,7 +284,7 @@ pub const ItemDropManager = struct { self.addWithIndexAndRotation(self, i, pos, vel, rot, itemStack, despawnTime, pickupCooldown); } - fn defaultAddWithIndexAndRotation(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: u32, pickupCooldown: u32) void { + fn defaultAddWithIndexAndRotation(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { self.mutex.lock(); defer self.mutex.unlock(); std.debug.assert(self.isEmpty.isSet(i)); @@ -330,20 +332,20 @@ pub const ItemDropManager = struct { fn updateEnt(self: *ItemDropManager, chunk: *Chunk, pos: *Vec3d, vel: *Vec3d, deltaTime: f64) void { std.debug.assert(!self.mutex.tryLock()); // Mutex must be locked! - const startedInABlock = checkBlocks(chunk, pos); + const startedInABlock = self.checkBlocks(chunk, pos); if(startedInABlock) { self.fixStuckInBlock(chunk, pos, vel, deltaTime); return; } - const drag: f64 = self.airDragFactor; - var acceleration: Vec3f = Vec3f{0, -self.gravity*deltaTime, 0}; + var drag: f64 = self.airDragFactor; + var acceleration: Vec3d = Vec3d{0, -self.gravity*deltaTime, 0}; // Update gravity: inline for(0..3) |i| { - const old = pos[i]; - pos[i] += vel[i]*deltaTime + acceleration[i]*deltaTime; + const old = pos.*[i]; + pos.*[i] += vel.*[i]*deltaTime + acceleration[i]*deltaTime; if(self.checkBlocks(chunk, pos)) { - pos[i] = old; - vel[i] *= 0.5; // Effectively performing binary search over multiple frames. + pos.*[i] = old; + vel.*[i] *= 0.5; // Effectively performing binary search over multiple frames. } drag += 0.5; // TODO: Calculate drag from block properties and add buoyancy. } @@ -357,7 +359,7 @@ pub const ItemDropManager = struct { const centeredPos = pos.* - @splat(3, @as(f64, 0.5)); const pos0 = vec.floatToInt(i32, @floor(centeredPos)); - var closestEmptyBlock = @splat(3, @splat(i32, -1)); + var closestEmptyBlock = @splat(3, @as(i32, -1)); var closestDist = std.math.floatMax(f64); var delta = Vec3i{0, 0, 0}; while(delta[0] <= 1) : (delta[0] += 1) { @@ -378,45 +380,45 @@ pub const ItemDropManager = struct { } vel.* = @splat(3, @as(f64, 0)); - const factor = 1; // TODO: Investigate what past me wanted to accomplish here. + const factor: f64 = 1; // TODO: Investigate what past me wanted to accomplish here. if(closestDist == std.math.floatMax(f64)) { // Surrounded by solid blocks → move upwards - vel[1] = factor; - pos[1] += vel[1]*deltaTime; + vel.*[1] = factor; + pos.*[1] += vel.*[1]*deltaTime; } else { vel.* = @splat(3, factor)*(vec.intToFloat(f64, pos0 + closestEmptyBlock) - centeredPos); pos.* += (vel.*)*@splat(3, deltaTime); } } - fn checkBlocks(self: *ItemDropManager, chunk: *Chunk, pos: *Vec3d) void { + fn checkBlocks(self: *ItemDropManager, chunk: *Chunk, pos: *Vec3d) bool { const lowerCornerPos = pos.* - @splat(3, radius); const pos0 = vec.floatToInt(i32, @floor(lowerCornerPos)); - const isSolid = self.checkBlock(chunk, pos, pos0); - if(pos[0] - @intToFloat(f64, pos0[0]) + diameter >= 1) { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 0}); - if(pos[1] - @intToFloat(f64, pos0[1]) + diameter >= 1) { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 0}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 0}); - if(pos[2] - @intToFloat(f64, pos0[2]) + diameter >= 1) { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 1}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 1}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{1, 1, 1}); + var isSolid = self.checkBlock(chunk, pos, pos0); + if(pos.*[0] - @intToFloat(f64, pos0[0]) + diameter >= 1) { + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 0}); + if(pos.*[1] - @intToFloat(f64, pos0[1]) + diameter >= 1) { + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 0}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 0}); + if(pos.*[2] - @intToFloat(f64, pos0[2]) + diameter >= 1) { + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{1, 1, 1}); } } else { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{1, 0, 1}); } } else { - if(pos[1] - @intToFloat(f64, pos0[1]) + diameter >= 1) { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 0}); - if(pos[2] - @intToFloat(f64, pos0[2]) + diameter >= 1) { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 1}); + if(pos.*[1] - @intToFloat(f64, pos0[1]) + diameter >= 1) { + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 0}); + if(pos.*[2] - @intToFloat(f64, pos0[2]) + diameter >= 1) { + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 1, 1}); } } else { - isSolid |= checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); + isSolid = isSolid or self.checkBlock(chunk, pos, pos0 + Vec3i{0, 0, 1}); } } return isSolid; @@ -461,7 +463,7 @@ pub const ClientItemDropManager = struct { .super = undefined, .lastTime = @truncate(i16, std.time.milliTimestamp()) -% settings.entityLookback, }; - try self.super.init(allocator, world); + try self.super.init(allocator, null, world.gravity); self.super.addWithIndexAndRotation = &overrideAddWithIndexAndRotation; self.interpolation.init( @ptrCast(*[maxf64Capacity]f64, self.super.list.items(.pos).ptr), @@ -506,7 +508,7 @@ pub const ClientItemDropManager = struct { self.lastTime = time; } - fn overrideAddWithIndexAndRotation(super: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: u32, pickupCooldown: u32) void { + fn overrideAddWithIndexAndRotation(super: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { { super.mutex.lock(); defer super.mutex.unlock(); diff --git a/src/main.zig b/src/main.zig index 614d3405..f193482a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -500,7 +500,7 @@ pub fn main() !void { logFile = std.fs.cwd().createFile("logs/latest.log", .{}) catch unreachable; defer logFile.close(); - threadPool = try utils.ThreadPool.init(globalAllocator, 1 + ((std.Thread.getCpuCount() catch 4) -| 3)); + threadPool = try utils.ThreadPool.init(globalAllocator, 1 + ((std.Thread.getCpuCount() catch 4) -| 2)); defer threadPool.deinit(); try settings.init(); diff --git a/src/models.zig b/src/models.zig index a852e4df..7e2c0371 100644 --- a/src/models.zig +++ b/src/models.zig @@ -202,7 +202,7 @@ var nameToIndex: std.StringHashMap(u16) = undefined; pub fn getModelIndex(string: []const u8) u16 { return nameToIndex.get(string) orelse { - std.log.err("Couldn't find voxelModel with name: {s}.", .{string}); + std.log.warn("Couldn't find voxelModel with name: {s}.", .{string}); return 0; }; } diff --git a/src/network.zig b/src/network.zig index e6d4dfc2..6aea9b3d 100644 --- a/src/network.zig +++ b/src/network.zig @@ -685,9 +685,9 @@ pub const Protocols = struct { .wz = std.mem.readIntBig(i32, remaining[8..12]), .voxelSize = @intCast(u31, std.mem.readIntBig(i32, remaining[12..16])), }; - _ = request; - _ = conn; - // TODO: Server.world.queueChunk(request, (User)conn); + if(conn.user) |user| { + try main.server.world.?.queueChunk(request, user); + } remaining = remaining[16..]; } } @@ -731,31 +731,21 @@ pub const Protocols = struct { } try renderer.RenderStructure.updateChunkMesh(ch); } - pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void { - var data = try main.threadAllocator.alloc(u8, 16 + 8*visData.visibles.items.len); - defer main.threadAllocator.free(data); - std.mem.writeIntBig(i32, data[0..4], visData.pos.wx); - std.mem.writeIntBig(i32, data[4..8], visData.pos.wy); - std.mem.writeIntBig(i32, data[8..12], visData.pos.wz); - std.mem.writeIntBig(i32, data[12..16], visData.pos.voxelSize); - var size = visData.visibles.items.len; - var x = data[16..][0..size]; - var y = data[16..][size..2*size]; - var z = data[16..][2*size..3*size]; - var neighbors = data[16..][3*size..4*size]; - var visibleBlocks = data[16..][4*size..]; - for(visData.visibles.items, 0..) |block, i| { - x[i] = block.x; - y[i] = block.y; - z[i] = block.z; - neighbors[i] = block.neighbors; - var blockTypeAndData = @as(u32, block.block.data) << 16 | block.block.typ; - std.mem.writeIntBig(u32, visibleBlocks[4*i..][0..4], blockTypeAndData); + pub fn sendChunk(conn: *Connection, ch: *chunk.Chunk) !void { + var uncompressedData: [4*ch.blocks.len]u8 = undefined; + for(&ch.blocks, 0..) |*block, i| { + std.mem.writeIntBig(u32, uncompressedData[4*i..][0..4], block.toInt()); } - - var compressed = try utils.Compression.deflate(main.threadAllocator, data); - defer main.threadAllocator.free(compressed); - try conn.sendImportant(id, compressed); + const compressedData = try utils.Compression.deflate(main.threadAllocator, &uncompressedData); + defer main.threadAllocator.free(compressedData); + const data =try main.threadAllocator.alloc(u8, 16 + compressedData.len); + defer main.threadAllocator.free(data); + std.mem.copy(u8, data[16..], compressedData); + std.mem.writeIntBig(i32, data[0..4], ch.pos.wx); + std.mem.writeIntBig(i32, data[4..8], ch.pos.wy); + std.mem.writeIntBig(i32, data[8..12], ch.pos.wz); + std.mem.writeIntBig(i32, data[12..16], ch.pos.voxelSize); + try conn.sendImportant(id, data); } }; pub const playerPosition = struct { @@ -1500,7 +1490,7 @@ pub const Connection = struct { var newIndex = self.lastIndex; var protocol = receivedPacket[newIndex]; newIndex += 1; - if(self.manager.world == null and protocol != Protocols.handShake.id) + if(self.manager.world == null and self.user == null and protocol != Protocols.handShake.id) return; // Determine the next packet length: diff --git a/src/renderer.zig b/src/renderer.zig index a29ce238..f1c5411c 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -554,7 +554,7 @@ pub const MenuBackGround = struct { } } if(fileList.items.len == 0) { - std.log.err("Couldn't find any background scene images in \"assets/backgrounds\".", .{}); + std.log.warn("Couldn't find any background scene images in \"assets/backgrounds\".", .{}); texture = .{.textureID = 0}; return; } @@ -972,7 +972,6 @@ pub const RenderStructure = struct { } pub fn deinit() void { - main.threadPool.clear(); for(storageLists) |storageList| { for(storageList) |nullChunkMesh| { if(nullChunkMesh) |chunkMesh| { diff --git a/src/server/server.zig b/src/server/server.zig index 9cc82b9e..a61d0c1b 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -8,6 +8,8 @@ const utils = main.utils; const vec = main.vec; const Vec3d = vec.Vec3d; +pub const ServerWorld = @import("world.zig").ServerWorld; + pub const User = struct { conn: *Connection, @@ -43,8 +45,8 @@ pub const User = struct { } pub fn deinit(self: *User) void { - main.globalAllocator.destroy(self); self.conn.deinit(); + main.globalAllocator.destroy(self); } // @Override // public void disconnect() { @@ -108,26 +110,29 @@ pub const User = struct { // } }; -const updatesPerSec: u32 = 20; +pub const updatesPerSec: u32 = 20; const updateNanoTime: u32 = 1000000000/20; -// TODO: -// public static ServerWorld world = null; + +pub var world: ?*ServerWorld = null; pub var users: std.ArrayList(*User) = undefined; pub var connectionManager: *ConnectionManager = undefined; -var running: bool = false; +var running: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false); var lastTime: i128 = undefined; -var mutex: std.Thread.Mutex = .{}; +pub var mutex: std.Thread.Mutex = .{}; -fn init() !void { +pub var thread: ?std.Thread = null; + +fn init(name: []const u8) !void { + std.debug.assert(world == null); // There can only be one world. users = std.ArrayList(*User).init(main.globalAllocator); lastTime = std.time.nanoTimestamp(); connectionManager = try ConnectionManager.init(main.settings.defaultPort, false); // TODO Configure the second argument in the server settings. // TODO: Load the assets. -// TODO: -// Server.world = new ServerWorld(args[0], null); + + world = try ServerWorld.init(name, null); if(true) { // singleplayer // TODO: Configure this in the server settings. const user = try User.init(connectionManager, "127.0.0.1:47650"); try connect(user); @@ -141,14 +146,15 @@ fn deinit() void { users.clearAndFree(); connectionManager.deinit(); connectionManager = undefined; - // TODO: -// if(world != null) -// world.cleanup(); -// world = null; + + if(world) |_world| { + _world.deinit(); + } + world = null; } fn update() !void { -// TODO: world.update(); + world.?.update(); mutex.lock(); for(users.items) |user| { user.update(); @@ -160,17 +166,17 @@ fn update() !void { // lastSentEntities = entities; } -pub fn start() !void { +pub fn start(name: []const u8) !void { var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){}; main.threadAllocator = gpa.allocator(); defer if(gpa.deinit()) { std.log.err("Memory leak", .{}); }; - std.debug.assert(!running); // There can only be one server. - try init(); + std.debug.assert(!running.load(.Monotonic)); // There can only be one server. + try init(name); defer deinit(); - running = true; - while(running) { + running.store(true, .Monotonic); + while(running.load(.Monotonic)) { const newTime = std.time.nanoTimestamp(); if(newTime -% lastTime < updateNanoTime) { std.time.sleep(@intCast(u64, lastTime +% updateNanoTime -% newTime)); @@ -185,7 +191,7 @@ pub fn start() !void { } pub fn stop() void { - running = false; + running.store(false, .Monotonic); } pub fn disconnect(user: *User) !void { diff --git a/src/server/world.zig b/src/server/world.zig new file mode 100644 index 00000000..8a698a88 --- /dev/null +++ b/src/server/world.zig @@ -0,0 +1,696 @@ +const std = @import("std"); + +const main = @import("root"); +const Block = main.blocks.Block; +const Cache = main.utils.Cache; +const chunk = main.chunk; +const ChunkPosition = chunk.ChunkPosition; +const Chunk = chunk.Chunk; +const files = main.files; +const utils = main.utils; +const ItemDropManager = main.itemdrop.ItemDropManager; +const ItemStack = main.items.ItemStack; +const JsonElement = main.JsonElement; +const vec = main.vec; +const Vec3i = vec.Vec3i; +const Vec3d = vec.Vec3d; +const Vec3f = vec.Vec3f; + +const server = @import("server.zig"); +const User = server.User; + +const ChunkManager = struct { + world: *ServerWorld, +//TODO: +// public final TerrainGenerationProfile terrainGenerationProfile; +// +// // There will be at most 1 GiB of chunks in here. TODO: Allow configuring this in the server settings. + const reducedChunkCacheMask = 2047; + var chunkCache: Cache(Chunk, reducedChunkCacheMask+1, 4, chunkDeinitFunctionForCache) = .{}; + // TODO: +// // There will be at most 1 GiB of map data in here. +// private static final int[] MAP_CACHE_MASK = { +// 7, // 256 MiB // 4(1 in best-case) maps are needed at most for each player. So 32 will be enough for 8(32 in best case) player groups. +// 31, // 256 MiB +// 63, // 128 MiB +// 255, // 128 MiB +// 511, // 64 MiB +// 2047, // 64 MiB +// }; +// @SuppressWarnings("unchecked") +// private final Cache[] mapCache = new Cache[] { +// new Cache<>(new MapFragment[MAP_CACHE_MASK[0] + 1][4]), +// new Cache<>(new MapFragment[MAP_CACHE_MASK[1] + 1][4]), +// new Cache<>(new MapFragment[MAP_CACHE_MASK[2] + 1][4]), +// new Cache<>(new MapFragment[MAP_CACHE_MASK[3] + 1][4]), +// new Cache<>(new MapFragment[MAP_CACHE_MASK[4] + 1][4]), +// new Cache<>(new MapFragment[MAP_CACHE_MASK[5] + 1][4]), +// }; + + const ChunkLoadTask = struct { + pos: ChunkPosition, + creationTime: i64, + source: ?*User, + + const vtable = utils.ThreadPool.VTable{ + .getPriority = @ptrCast(*const fn(*anyopaque) f32, &getPriority), + .isStillNeeded = @ptrCast(*const fn(*anyopaque) bool, &isStillNeeded), + .run = @ptrCast(*const fn(*anyopaque) void, &run), + .clean = @ptrCast(*const fn(*anyopaque) void, &clean), + }; + + pub fn schedule(pos: ChunkPosition, source: ?*User) !void { + var task = try main.globalAllocator.create(ChunkLoadTask); + task.* = ChunkLoadTask { + .pos = pos, + .creationTime = std.time.milliTimestamp(), + .source = source, + }; + try main.threadPool.addTask(task, &vtable); + } + + pub fn getPriority(self: *ChunkLoadTask) f32 { + _ = self; + return 0; + // TODO: return self.pos.getPriority(self.source.player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time. + } + + pub fn isStillNeeded(self: *ChunkLoadTask) bool { + // TODO: + if(self.source) |source| { + _ = source; + // TODO: This requires not garbage-collecting the source User. +// boolean isConnected = false; +// for(User user : Server.users) { +// if(source == user) { +// isConnected = true; +// break; +// } +// } +// if(!isConnected) { +// return false; +// } + } + if(std.time.milliTimestamp() - self.creationTime > 10000) { // Only remove stuff after 10 seconds to account for trouble when for example teleporting. + // TODO: +// for(User user : Server.users) { +// double minDistSquare = ch.getMinDistanceSquared(user.player.getPosition().x, user.player.getPosition().y, user.player.getPosition().z); +// // ↓ Margin for error. (diagonal of 1 chunk) +// double targetRenderDistance = (user.renderDistance*Chunk.chunkSize + Chunk.chunkSize*Math.sqrt(3));//*Math.pow(user.LODFactor, Math.log(ch.voxelSize)/Math.log(2)); +// if(ch.voxelSize != 1) { +// targetRenderDistance *= ch.voxelSize*user.LODFactor; +// } +// if(minDistSquare <= targetRenderDistance*targetRenderDistance) { +// return true; +// } +// } +// return false; + } + return true; + } + + pub fn run(self: *ChunkLoadTask) void { + defer self.clean(); + generateChunk(self.pos, self.source) catch |err| { + std.log.err("Got error while generating chunk {}: {s}", .{self.pos, @errorName(err)}); + }; + } + + pub fn clean(self: *ChunkLoadTask) void { + main.globalAllocator.destroy(self); + } + }; + + pub fn init(world: *ServerWorld, settings: JsonElement) !ChunkManager { + _ = settings; + const self = ChunkManager { + .world = world, +//TODO: terrainGenerationProfile = new TerrainGenerationProfile(settings, world.getCurrentRegistries(), world.getSeed()); + }; + // TODO: +// CaveBiomeMap.init(terrainGenerationProfile); +// CaveMap.init(terrainGenerationProfile); +// ClimateMap.init(terrainGenerationProfile); + return self; + } + + pub fn deinit(self: ChunkManager) void { + _ = self; + main.assets.unloadAssets(); + // TODO: +// for(Cache cache : mapCache) { +// cache.clear(); +// } +// for(int i = 0; i < 5; i++) { // Saving one chunk may create and update a new lower resolution chunk. +// +// for(ReducedChunk[] array : reducedChunkCache.cache) { +// array = Arrays.copyOf(array, array.length); // Make a copy to prevent issues if the cache gets resorted during cleanup. +// for(ReducedChunk chunk : array) { +// if (chunk != null) +// chunk.clean(); +// } +// } +// } +// for(Cache cache : mapCache) { +// cache.clear(); +// } +// ThreadPool.clear(); +// CaveBiomeMap.cleanup(); +// CaveMap.cleanup(); +// ClimateMap.cleanup(); +// reducedChunkCache.clear(); +// ChunkIO.clean(); + chunkCache.clear(); + } + + pub fn queueChunk(self: ChunkManager, pos: ChunkPosition, source: ?*User) !void { + _ = self; + try ChunkLoadTask.schedule(pos, source); + } + + pub fn generateChunk(pos: ChunkPosition, source: ?*User) !void { + const ch = try getOrGenerateChunk(pos); + if(source) |_source| { + try main.network.Protocols.chunkTransmission.sendChunk(_source.conn, ch); + } else { // TODO: This feature was temporarily removed to keep compatibility with the zig version. + server.mutex.lock(); + defer server.mutex.unlock(); + for(server.users.items) |user| { + try main.network.Protocols.chunkTransmission.sendChunk(user.conn, ch); + } + } + } +// +// public MapFragment getOrGenerateMapFragment(int wx, int wz, int voxelSize) { +// wx &= ~MapFragment.MAP_MASK; +// wz &= ~MapFragment.MAP_MASK; +// +// MapFragmentCompare data = new MapFragmentCompare(wx, wz, voxelSize); +// int index = CubyzMath.binaryLog(voxelSize); +// int hash = data.hashCode() & MAP_CACHE_MASK[index]; +// +// MapFragment res = mapCache[index].find(data, hash); +// if (res != null) return res; +// +// synchronized(mapCache[index].cache[hash]) { +// res = mapCache[index].find(data, hash); +// if (res != null) return res; +// +// // Generate a new map fragment: +// res = new MapFragment(wx, wz, voxelSize); +// terrainGenerationProfile.mapFragmentGenerator.generateMapFragment(res, world.getSeed()); +// mapCache[index].addToCache(res, hash); +// } +// return res; +// } + + fn chunkInitFunctionForCache(pos: ChunkPosition) !*Chunk { + const ch = try main.globalAllocator.create(Chunk); + ch.init(pos); + for(&ch.blocks) |*block| { + block.* = Block{.typ = 0, .data = 0}; + } + // TODO: res.generate(world.getSeed(), terrainGenerationProfile); + return ch; + } + + fn chunkDeinitFunctionForCache(ch: *Chunk) void { + main.globalAllocator.destroy(ch); + // TODO: Store chunk. + } + /// Generates a normal chunk at a given location, or if possible gets it from the cache. + fn getOrGenerateChunk(pos: ChunkPosition) !*Chunk { + return try chunkCache.findOrCreate(pos, chunkInitFunctionForCache); + } + + pub fn getChunkFromCache(pos: ChunkPosition) ?*Chunk { + return chunkCache.find(pos); + } + +// public void forceSave() { +// for(int i = 0; i < 5; i++) { // Saving one chunk may create and update a new lower resolution chunk. +// reducedChunkCache.foreach(Chunk::save); +// } +// } +}; + +const WorldIO = struct { + const worldDataVersion: u32 = 2; + + dir: files.Dir, + world: *ServerWorld, + + pub fn init(dir: files.Dir, world: *ServerWorld) WorldIO { + return WorldIO { + .dir = dir, + .world = world, + }; + } + + pub fn deinit(self: *WorldIO) void { + self.dir.close(); + } + + pub fn hasWorldData(self: WorldIO) bool { + return self.dir.hasFile("world.dat"); + } + + /// Load the seed, which is needed before custom item and ore generation. + pub fn loadWorldSeed(self: WorldIO) !u64 { + const worldData: JsonElement = try self.dir.readToJson(main.threadAllocator, "world.dat"); + defer worldData.free(main.threadAllocator); + if(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 worldData.get(u64, "seed", 0); + } + + pub fn loadWorldData(self: WorldIO) !void { + const worldData: JsonElement = try self.dir.readToJson(main.threadAllocator, "world.dat"); + defer worldData.free(main.threadAllocator); + + const entityJson = worldData.getChild("entities"); + _ = entityJson; + +// Entity[] entities = new Entity[entityJson.array.size()]; +// for(int i = 0; i < entities.length; i++) { +// // TODO: Only load entities that are in loaded chunks. +// entities[i] = EntityIO.loadEntity((JsonObject)entityJson.array.get(i), world); +// } +// world.setEntities(entities); + self.world.doGameTimeCycle = worldData.get(bool, "doGameTimeCycle", true); + self.world.gameTime = worldData.get(i64, "gameTime", 0); + const spawnData = worldData.getChild("spawn"); + self.world.spawn[0] = spawnData.get(i32, "x", 0); + self.world.spawn[1] = spawnData.get(i32, "y", 0); + self.world.spawn[2] = spawnData.get(i32, "z", 0); + } + + pub fn saveWorldData(self: WorldIO) !void { + const worldData: JsonElement = try JsonElement.initObject(main.threadAllocator); + defer worldData.free(main.threadAllocator); + try worldData.put("version", worldDataVersion); + try worldData.put("seed", self.world.seed); + try worldData.put("doGameTimeCycle", self.world.doGameTimeCycle); + try worldData.put("gameTime", self.world.gameTime); + // TODO: +// worldData.put("entityCount", world.getEntities().length); + const spawnData = try JsonElement.initObject(main.threadAllocator); + try spawnData.put("x", self.world.spawn[0]); + try spawnData.put("y", self.world.spawn[1]); + try spawnData.put("z", self.world.spawn[2]); + try worldData.put("spawn", spawnData); + // TODO: +// JsonArray entityData = new JsonArray(); +// worldData.put("entities", entityData); +// // TODO: Store entities per chunk. +// for (Entity ent : world.getEntities()) { +// if (ent != null && ent.getType().getClass() != PlayerEntity.class) { +// entityData.add(ent.save()); +// } +// } + try self.dir.writeJson("world.dat", worldData); + } +}; + +pub const ServerWorld = struct { + pub const dayCycle: u31 = 12000; // Length of one in-game day in units of 100ms. Midnight is at DAY_CYCLE/2. Sunrise and sunset each take about 1/16 of the day. Currently set to 20 minutes + pub const earthGravity: f32 = 9.81; + + itemDropManager: ItemDropManager = undefined, + blockPalette: *main.assets.BlockPalette = undefined, + chunkManager: ChunkManager = undefined, +// TODO: protected HashMap metaChunks = new HashMap(); +// TODO: protected NormalChunk[] chunks = new NormalChunk[0]; + + generated: bool = false, + + gameTime: i64 = 0, + milliTime: i64, + lastUpdateTime: i64, + lastUnimportantDataSent: i64, + doGameTimeCycle: bool = true, + gravity: f32 = earthGravity, + + seed: u64, + name: []const u8, + spawn: Vec3i = undefined, + + wio: WorldIO = undefined, + // TODO: +// protected ArrayList entities = new ArrayList<>(); + + pub fn init(name: []const u8, nullGeneratorSettings: ?JsonElement) !*ServerWorld { + const self = try main.globalAllocator.create(ServerWorld); + self.* = ServerWorld { + .lastUpdateTime = std.time.milliTimestamp(), + .milliTime = std.time.milliTimestamp(), + .lastUnimportantDataSent = std.time.milliTimestamp(), + .seed = @bitCast(u64, @truncate(i64, std.time.nanoTimestamp())), + .name = name, + }; + try self.itemDropManager.init(main.globalAllocator, self, self.gravity); + + var loadArena = std.heap.ArenaAllocator.init(main.threadAllocator); + defer loadArena.deinit(); + const arenaAllocator = loadArena.allocator(); + var buf: [32768]u8 = undefined; + var generatorSettings: JsonElement = undefined; + if(nullGeneratorSettings) |_generatorSettings| { + generatorSettings = _generatorSettings; + // Store generator settings: + try files.writeJson(try std.fmt.bufPrint(&buf, "saves/{s}/generatorSettings.json", .{name}), generatorSettings); + } else { // Read the generator settings: + generatorSettings = try files.readToJson(arenaAllocator, try std.fmt.bufPrint(&buf, "saves/{s}/generatorSettings.json", .{name})); + } + self.wio = WorldIO.init(try files.openDir(try std.fmt.bufPrint(&buf, "saves/{s}", .{name})), self); + const blockPaletteJson = try files.readToJson(arenaAllocator, try std.fmt.bufPrint(&buf, "saves/{s}/palette.json", .{name})); + self.blockPalette = try main.assets.BlockPalette.init(main.globalAllocator, blockPaletteJson.getChild("blocks")); // TODO: Figure out why this is inconsistent with the save call. + + if(self.wio.hasWorldData()) { + self.seed = try self.wio.loadWorldSeed(); + self.generated = true; + try main.assets.loadWorldAssets(try std.fmt.bufPrint(&buf, "saves/{s}/assets/", .{name}), self.blockPalette); + } else { + self.seed = main.random.nextInt(u48, &main.seed); + try main.assets.loadWorldAssets(try std.fmt.bufPrint(&buf, "saves/{s}/assets/", .{name}), self.blockPalette); + try self.wio.saveWorldData(); + } + // Store the block palette now that everything is loaded. + try files.writeJson(try std.fmt.bufPrint(&buf, "saves/{s}/palette.json", .{name}), try self.blockPalette.save(arenaAllocator)); + +// TODO: // Call mods for this new world. Mods sometimes need to do extra stuff for the specific world. +// ModLoader.postWorldGen(registries); +// + self.chunkManager = try ChunkManager.init(self, generatorSettings); + try self.generate(); + try self.itemDropManager.loadFrom(try files.readToJson(arenaAllocator, try std.fmt.bufPrint(&buf, "saves/{s}/items.json", .{name}))); + return self; + } + + pub fn deinit(self: *ServerWorld) void { + self.itemDropManager.deinit(); + self.blockPalette.deinit(); + self.chunkManager.deinit(); + self.wio.deinit(); + main.globalAllocator.destroy(self); + } + + fn generate(self: *ServerWorld) !void { + try self.wio.loadWorldData(); // load data here in order for entities to also be loaded. + + if(!self.generated) { + var seed = @bitCast(u64, @truncate(i64, std.time.nanoTimestamp())); + std.log.info("Finding position..", .{}); + for(0..1000) |_| { + self.spawn[0] = main.random.nextIntBounded(u31, &seed, 65536); + self.spawn[2] = main.random.nextIntBounded(u31, &seed, 65536); + std.log.info("Trying ({}, {})", .{self.spawn[0], self.spawn[2]}); + if(try self.isValidSpawnLocation(self.spawn[0], self.spawn[2])) break; + } + // TODO: spawn[1] = chunkManager.getOrGenerateMapFragment(spawn.x, spawn.z, 1).getHeight(spawn.x, spawn.z); + } + self.generated = true; + try self.wio.saveWorldData(); + } + +// TODO: +// public Player findPlayer(User user) { +// JsonObject playerData = JsonParser.parseObjectFromFile("saves/" + name + "/players/" + Utils.escapeFolderName(user.name) + ".json"); +// Player player = new Player(this, user.name); +// if(playerData.map.isEmpty()) { +// // Generate a new player: +// player.setPosition(spawn); +// } else { +// player.loadFrom(playerData); +// } +// addEntity(player); +// return player; +// } +// +// private void savePlayers() { +// for(User user : Server.users) { +// try { +// File file = new File("saves/" + name + "/players/" + Utils.escapeFolderName(user.name) + ".json"); +// file.getParentFile().mkdirs(); +// PrintWriter writer = new PrintWriter(new FileOutputStream("saves/" + name + "/players/" + Utils.escapeFolderName(user.name) + ".json"), false, StandardCharsets.UTF_8); +// user.player.save().writeObjectToStream(writer); +// writer.close(); +// } catch(FileNotFoundException e) { +// Logger.error(e); +// } +// } +// } + pub fn forceSave(self: *ServerWorld) !void { + // TODO: +// for(MetaChunk chunk : metaChunks.values().toArray(new MetaChunk[0])) { +// if (chunk != null) chunk.save(); +// } + self.wio.saveWorldData(); + // TODO: +// savePlayers(); +// chunkManager.forceSave(); +// ChunkIO.save(); + const itemDropJson = self.itemDropManager.store(main.threadAllocator); + defer itemDropJson.free(main.threadAllocator); + var buf: [32768]u8 = undefined; + files.writeJson(try std.fmt.bufPrint(&buf, "saves/{s}/items.json", .{self.name}), itemDropJson); + } +// TODO: +// public void addEntity(Entity ent) { +// entities.add(ent); +// } +// +// public void removeEntity(Entity ent) { +// entities.remove(ent); +// } +// +// public void setEntities(Entity[] arr) { +// entities = new ArrayList<>(arr.length); +// for (Entity e : arr) { +// entities.add(e); +// } +// } + + fn isValidSpawnLocation(self: *ServerWorld, x: i32, z: i32) !bool { + _ = self; + _ = x; + _ = z; + return true; +// TODO: return chunkManager.getOrGenerateMapFragment(x, z, 32).getBiome(x, z).isValidPlayerSpawn; + } + + pub fn dropWithCooldown(self: *ServerWorld, stack: ItemStack, pos: Vec3d, dir: Vec3f, velocity: f32, pickupCooldown: u32) !void { + const vel = vec.floatCast(f64, dir*@splat(3, velocity)); + try self.itemDropManager.add(pos, vel, stack, server.updatesPerSec*900, pickupCooldown); + } + + pub fn drop(self: *ServerWorld, stack: ItemStack, pos: Vec3d, dir: Vec3f, velocity: f32) !void { + try self.dropWithCooldown(stack, pos, dir, velocity, 0); + } + +// TODO: +// @Override +// public void updateBlock(int x, int y, int z, int newBlock) { +// NormalChunk ch = getChunk(x, y, z); +// if (ch != null) { +// int old = ch.getBlock(x & Chunk.chunkMask, y & Chunk.chunkMask, z & Chunk.chunkMask); +// if(old == newBlock) return; +// ch.updateBlock(x & Chunk.chunkMask, y & Chunk.chunkMask, z & Chunk.chunkMask, newBlock); +// // Send the block update to all players: +// for(User user : Server.users) { +// Protocols.BLOCK_UPDATE.send(user, x, y, z, newBlock); +// } +// if((old & Blocks.TYPE_MASK) == (newBlock & Blocks.TYPE_MASK)) return; +// for(BlockDrop drop : Blocks.blockDrops(old)) { +// int amount = (int)(drop.amount); +// float randomPart = drop.amount - amount; +// if (Math.random() < randomPart) amount++; +// if (amount > 0) { +// itemEntityManager.add(x, y, z, 0, 0, 0, new ItemStack(drop.item, amount), 30*900); +// } +// } +// } +// } + + pub fn update(self: *ServerWorld) void { + const newTime = std.time.milliTimestamp(); + var deltaTime = @intToFloat(f32, newTime - self.lastUpdateTime)/1000.0; + self.lastUpdateTime = newTime; + if(deltaTime > 0.3) { + std.log.warn("Update time is getting too high. It's alread at {} s!", .{deltaTime}); + deltaTime = 0.3; + } + + while(self.milliTime + 100 < newTime) { + self.milliTime += 100; + if(self.doGameTimeCycle) self.gameTime += 1; // gameTime is measured in 100ms. + } + if(self.lastUnimportantDataSent + 2000 < newTime) {// Send unimportant data every ~2s. + self.lastUnimportantDataSent = newTime; + // TODO: +// for(User user : Server.users) { +// Protocols.GENERIC_UPDATE.sendTimeAndBiome(user, this); +// } + } + // TODO: +// // Entities +// for (int i = 0; i < entities.size(); i++) { +// Entity en = entities.get(i); +// en.update(deltaTime); +// // Check item entities: +// if (en.getInventory() != null) { +// itemEntityManager.checkEntity(en); +// } +// } + + // Item Entities + self.itemDropManager.update(deltaTime); + // TODO: +// // Block Entities +// for(MetaChunk chunk : metaChunks.values()) { +// chunk.updateBlockEntities(); +// } +// +// // Liquids +// if (gameTime % 3 == 0) { +// //Profiler.startProfiling(); +// for(MetaChunk chunk : metaChunks.values()) { +// chunk.liquidUpdate(); +// } +// //Profiler.printProfileTime("liquid-update"); +// } +// +// seek(); + } + +// TODO: +// @Override + pub fn queueChunks(self: *ServerWorld, positions: []ChunkPosition, source: ?*User) !void { + for(positions) |pos| { + try self.queueChunk(pos, source); + } + } + + pub fn queueChunk(self: *ServerWorld, pos: ChunkPosition, source: ?*User) !void { + try self.chunkManager.queueChunk(pos, source); + } + + pub fn seek() !void { + // TODO: Remove this MetaChunk stuff. It wasn't really useful and made everything needlessly complicated. +// // Care about the metaChunks: +// HashMap oldMetaChunks = new HashMap<>(metaChunks); +// HashMap newMetaChunks = new HashMap<>(); +// for(User user : Server.users) { +// ArrayList chunkList = new ArrayList<>(); +// int metaRenderDistance = (int)Math.ceil(Settings.entityDistance/(float)(MetaChunk.metaChunkSize*Chunk.chunkSize)); +// int x0 = (int)user.player.getPosition().x >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// int y0 = (int)user.player.getPosition().y >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// int z0 = (int)user.player.getPosition().z >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// for(int metaX = x0 - metaRenderDistance; metaX <= x0 + metaRenderDistance + 1; metaX++) { +// for(int metaY = y0 - metaRenderDistance; metaY <= y0 + metaRenderDistance + 1; metaY++) { +// for(int metaZ = z0 - metaRenderDistance; metaZ <= z0 + metaRenderDistance + 1; metaZ++) { +// HashMapKey3D key = new HashMapKey3D(metaX, metaY, metaZ); +// if(newMetaChunks.containsKey(key)) continue; // It was already updated from another players perspective. +// // Check if it already exists: +// MetaChunk metaChunk = oldMetaChunks.get(key); +// oldMetaChunks.remove(key); +// if (metaChunk == null) { +// metaChunk = new MetaChunk(metaX *(MetaChunk.metaChunkSize*Chunk.chunkSize), metaY*(MetaChunk.metaChunkSize*Chunk.chunkSize), metaZ *(MetaChunk.metaChunkSize*Chunk.chunkSize), this); +// } +// newMetaChunks.put(key, metaChunk); +// metaChunk.update(Settings.entityDistance, chunkList); +// } +// } +// } +// oldMetaChunks.forEach((key, chunk) -> { +// chunk.clean(); +// }); +// chunks = chunkList.toArray(new NormalChunk[0]); +// metaChunks = newMetaChunks; +// } + } +// +// public MetaChunk getMetaChunk(int wx, int wy, int wz) { +// // Test if the metachunk exists: +// int metaX = wx >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// int metaY = wy >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// int metaZ = wz >> (MetaChunk.metaChunkShift + Chunk.chunkShift); +// HashMapKey3D key = new HashMapKey3D(metaX, metaY, metaZ); +// return metaChunks.get(key); +// } + + pub fn getChunk(self: *ServerWorld, x: i32, y: i32, z: i32) ?*Chunk { + _ = self; + _ = x; + _ = y; + _ = z; + // TODO: +// MetaChunk meta = getMetaChunk(wx, wy, wz); +// if (meta != null) { +// return meta.getChunk(wx, wy, wz); +// } + return null; + } +// TODO: +// public BlockEntity getBlockEntity(int x, int y, int z) { +// /*BlockInstance bi = getBlockInstance(x, y, z); +// Chunk ck = _getNoGenerateChunk(bi.getX() >> NormalChunk.chunkShift, bi.getZ() >> NormalChunk.chunkShift); +// return ck.blockEntities().get(bi);*/ +// return null; // TODO: Work on BlockEntities! +// } +// public NormalChunk[] getChunks() { +// return chunks; +// } +// +// public Entity[] getEntities() { +// return entities.toArray(new Entity[0]); +// } +// +// public int getHeight(int wx, int wz) { +// return (int)chunkManager.getOrGenerateMapFragment(wx, wz, 1).getHeight(wx, wz); +// } +// @Override +// public void cleanup() { +// // Be sure to dereference and finalize the maximum of things +// try { +// for(MetaChunk chunk : metaChunks.values()) { +// if (chunk != null) chunk.clean(); +// } +// chunkManager.forceSave(); +// ChunkIO.save(); +// +// chunkManager.cleanup(); +// +// ChunkIO.clean(); +// +// wio.saveWorldData(); +// savePlayers(); +// JsonParser.storeToFile(itemEntityManager.store(), "saves/" + name + "/items.json"); +// metaChunks = null; +// } catch (Exception e) { +// Logger.error(e); +// } +// } +// @Override +// public CurrentWorldRegistries getCurrentRegistries() { +// return registries; +// } +// +// public Biome getBiome(int wx, int wy, int wz) { +// return new InterpolatableCaveBiomeMap(new ChunkData( +// wx & ~CaveBiomeMapFragment.CAVE_BIOME_MASK, +// wy & ~CaveBiomeMapFragment.CAVE_BIOME_MASK, +// wz & ~CaveBiomeMapFragment.CAVE_BIOME_MASK, 1 +// ), 0).getRoughBiome(wx, wy, wz, null, true); +// } + + pub fn getBlock(self: *ServerWorld, x: i32, y: i32, z: i32) Block { + if(self.getChunk(x, y, z)) |ch| { + return ch.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); + } + return Block {.typ = 0, .data = 0}; + } + +}; diff --git a/src/utils.zig b/src/utils.zig index 8edaa989..9494dc72 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -7,7 +7,7 @@ pub const Compression = struct { pub fn deflate(allocator: Allocator, data: []const u8) ![]u8 { var result = std.ArrayList(u8).init(allocator); var comp = try std.compress.deflate.compressor(main.threadAllocator, result.writer(), .{.level = .default_compression}); - try comp.write(data); + _ = try comp.write(data); try comp.close(); comp.deinit(); return result.toOwnedSlice(); @@ -409,11 +409,12 @@ pub fn Cache(comptime T: type, comptime numberOfBuckets: u32, comptime bucketSiz cacheMisses: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0), /// Tries to find the entry that fits to the supplied hashable. - pub fn find(self: *@This(), compare: anytype, index: u32) void { + pub fn find(self: *@This(), compareAndHash: anytype) void { + const index: u32 = compareAndHash.hashCode() & hashMask; @atomicRmw(u32, &self.cacheRequests.value, .Add, 1, .Monotonic); self.buckets[index].mutex.lock(); defer self.buckets[index].mutex.unlock(); - if(self.buckets[index].find(compare)) |item| { + if(self.buckets[index].find(compareAndHash)) |item| { return item; } @atomicRmw(u32, &self.cacheMisses.value, .Add, 1, .Monotonic);