mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 19:28:49 -04:00
Add a ServerWorld which at the moment generates empty chunks.
This commit is contained in:
parent
38601dc42c
commit
cc97c0d296
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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 {
|
||||
|
@ -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| {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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| {
|
||||
|
@ -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
696
src/server/world.zig
Normal 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};
|
||||
}
|
||||
|
||||
};
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user