Add a ServerWorld which at the moment generates empty chunks.

This commit is contained in:
IntegratedQuantum 2023-04-13 21:04:46 +02:00
parent 38601dc42c
commit cc97c0d296
14 changed files with 888 additions and 126 deletions

View File

@ -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();
}

View File

@ -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 {

View File

@ -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));

View File

@ -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);
}
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;
}
};

View File

@ -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 {

View File

@ -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| {

View File

@ -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();

View File

@ -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();

View File

@ -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;
};
}

View File

@ -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:

View File

@ -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| {

View File

@ -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 {

696
src/server/world.zig Normal file
View File

@ -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<MapFragment>[] 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<MapFragment> 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<MapFragment> 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<HashMapKey3D, MetaChunk> metaChunks = new HashMap<HashMapKey3D, MetaChunk>();
// 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<Entity> 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<HashMapKey3D, MetaChunk> oldMetaChunks = new HashMap<>(metaChunks);
// HashMap<HashMapKey3D, MetaChunk> newMetaChunks = new HashMap<>();
// for(User user : Server.users) {
// ArrayList<NormalChunk> 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};
}
};

View File

@ -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);