diff --git a/build.zig b/build.zig index bfb95c41..89387763 100644 --- a/build.zig +++ b/build.zig @@ -45,6 +45,7 @@ pub fn build(b: *std.build.Builder) !void { } exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c", "lib/stb_image_write.c"}, &[_][]const u8{"-g", "-O3"}); exe.addAnonymousModule("gui", .{.source_file = .{.path = "src/gui/gui.zig"}}); + exe.addAnonymousModule("server", .{.source_file = .{.path = "src/server/server.zig"}}); const harfbuzzModule = freetype.harfbuzzModule(b); const freetypeModule = harfbuzzModule.dependencies.get("freetype").?; exe.addModule("harfbuzz", harfbuzzModule); diff --git a/src/gui/GuiWindow.zig b/src/gui/GuiWindow.zig index 69a95e57..4e7f9a45 100644 --- a/src/gui/GuiWindow.zig +++ b/src/gui/GuiWindow.zig @@ -155,7 +155,9 @@ pub fn mainButtonReleased(self: *GuiWindow, mousePosition: Vec2f) void { if(mousePosition[0] - self.pos[0] > self.size[0] - 32*self.scale) { if(mousePosition[0] - self.pos[0] > self.size[0] - 16*self.scale) { // Close - gui.closeWindow(self); + gui.closeWindow(self) catch |err| { + std.log.err("Got error while trying to close a window: {s}", .{@errorName(err)}); + }; return; } else { // Zoom out diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 6c01ee1a..1c7f63b5 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -34,6 +34,85 @@ pub var scale: f32 = undefined; pub var hoveredItemSlot: ?*ItemSlot = null; pub var hoveredCraftingSlot: ?*CraftingResultSlot = null; +const GuiCommandQueue = struct { + const Action = enum { + open, + close, + }; + const Command = struct { + window: *GuiWindow, + action: Action, + }; + + var commands: std.ArrayList(Command) = undefined; + var mutex: std.Thread.Mutex = .{}; + + fn init() void { + mutex.lock(); + defer mutex.unlock(); + commands = std.ArrayList(Command).init(main.globalAllocator); + } + + fn deinit() void { + mutex.lock(); + defer mutex.unlock(); + commands.deinit(); + } + + fn scheduleCommand(command: Command) !void { + mutex.lock(); + defer mutex.unlock(); + try commands.append(command); + } + + fn executeCommands() !void { + mutex.lock(); + defer mutex.unlock(); + for(commands.items) |command| { + switch(command.action) { + .open => { + try executeOpenWindowCommand(command.window); + }, + .close => { + executeCloseWindowCommand(command.window); + } + } + } + commands.clearRetainingCapacity(); + } + + fn executeOpenWindowCommand(window: *GuiWindow) !void { + std.debug.assert(!mutex.tryLock()); // mutex must be locked. + defer updateWindowPositions(); + for(openWindows.items, 0..) |_openWindow, i| { + if(_openWindow == window) { + _ = openWindows.swapRemove(i); + openWindows.appendAssumeCapacity(window); + selectedWindow = null; + return; + } + } + try openWindows.append(window); + try window.onOpenFn(); + selectedWindow = null; + } + + fn executeCloseWindowCommand(window: *GuiWindow) void { + std.debug.assert(!mutex.tryLock()); // mutex must be locked. + defer updateWindowPositions(); + if(selectedWindow == window) { + selectedWindow = null; + } + for(openWindows.items, 0..) |_openWindow, i| { + if(_openWindow == window) { + _ = openWindows.swapRemove(i); + break; + } + } + window.onCloseFn(); + } +}; + pub const Callback = struct { callback: ?*const fn(usize) void = null, arg: usize = 0, @@ -46,6 +125,7 @@ pub const Callback = struct { }; pub fn init() !void { + GuiCommandQueue.init(); windowList = std.ArrayList(*GuiWindow).init(main.globalAllocator); hudWindows = std.ArrayList(*GuiWindow).init(main.globalAllocator); openWindows = std.ArrayList(*GuiWindow).init(main.globalAllocator); @@ -104,6 +184,7 @@ pub fn deinit() void { } } inventory.deinit(); + GuiCommandQueue.deinit(); } fn save() !void { @@ -241,18 +322,7 @@ pub fn openWindow(id: []const u8) Allocator.Error!void { } pub fn openWindowFromRef(window: *GuiWindow) Allocator.Error!void { - defer updateWindowPositions(); - for(openWindows.items, 0..) |_openWindow, i| { - if(_openWindow == window) { - _ = openWindows.swapRemove(i); - openWindows.appendAssumeCapacity(window); - selectedWindow = null; - return; - } - } - try openWindows.append(window); - try window.onOpenFn(); - selectedWindow = null; + try GuiCommandQueue.scheduleCommand(.{.action = .open, .window = window}); } pub fn toggleWindow(id: []const u8) Allocator.Error!void { @@ -295,18 +365,8 @@ pub fn openWindowCallback(comptime id: []const u8) Callback { }; } -pub fn closeWindow(window: *GuiWindow) void { - defer updateWindowPositions(); - if(selectedWindow == window) { - selectedWindow = null; - } - for(openWindows.items, 0..) |_openWindow, i| { - if(_openWindow == window) { - _ = openWindows.swapRemove(i); - break; - } - } - window.onCloseFn(); +pub fn closeWindow(window: *GuiWindow) !void { + try GuiCommandQueue.scheduleCommand(.{.action = .close, .window = window}); } pub fn setSelectedTextInput(newSelectedTextInput: ?*TextInput) void { @@ -468,6 +528,7 @@ pub fn updateWindowPositions() void { pub fn updateAndRenderGui() !void { const mousePos = main.Window.getMousePosition()/@splat(2, scale); hoveredAWindow = false; + try GuiCommandQueue.executeCommands(); if(!main.Window.grabbed) { if(selectedWindow) |selected| { try selected.updateSelected(mousePos); @@ -604,7 +665,7 @@ pub const inventory = struct { } fn render(mousePos: Vec2f) !void { - carriedItemSlot.pos = mousePos; + carriedItemSlot.pos = mousePos - Vec2f{12, 12}; try carriedItemSlot.render(.{0, 0}); // Draw tooltip: if(carriedItemStack.amount == 0) if(hoveredItemSlot) |hovered| { diff --git a/src/gui/windows/change_name.zig b/src/gui/windows/change_name.zig index 32d6c460..aeb1d185 100644 --- a/src/gui/windows/change_name.zig +++ b/src/gui/windows/change_name.zig @@ -29,7 +29,10 @@ fn apply(_: usize) void { return; }; - gui.closeWindow(&window); + gui.closeWindow(&window) catch |err| { + std.log.err("Encountered error in change_name.apply while closing window: {s}", .{@errorName(err)}); + return; + }; if(oldName.len == 0) { gui.openWindow("main") catch |err| { std.log.err("Encountered error in change_name.apply: {s}", .{@errorName(err)}); diff --git a/src/gui/windows/multiplayer.zig b/src/gui/windows/multiplayer.zig index 77a63278..755900a1 100644 --- a/src/gui/windows/multiplayer.zig +++ b/src/gui/windows/multiplayer.zig @@ -71,7 +71,9 @@ fn join(_: usize) void { std.log.err("No connection found. Cannot connect.", .{}); } for(gui.openWindows.items) |openWindow| { - gui.closeWindow(openWindow); + gui.closeWindow(openWindow) catch |err| { + std.log.err("Encountered error while opening world: {s}", .{@errorName(err)}); + }; } gui.openHud() catch |err| { std.log.err("Encountered error while opening world: {s}", .{@errorName(err)}); diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig index 307fa733..e2c42aa0 100644 --- a/src/gui/windows/save_selection.zig +++ b/src/gui/windows/save_selection.zig @@ -28,8 +28,27 @@ 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}); -// new Thread(() -> Server.main(new String[] {name}), "Server Thread").start(); -// Cubyz.gameUI.setMenu(null, false); // hide from UISystem.back() + _ = std.Thread.spawn(.{}, main.server.start, .{}) catch |err| { + std.log.err("Encountered error while starting server thread: {s}", .{@errorName(err)}); + }; + + const connection = ConnectionManager.init(main.settings.defaultPort+1, false) catch |err| { + std.log.err("Encountered error while opening connection: {s}", .{@errorName(err)}); + return; + }; + connection.world = &main.game.testWorld; + main.game.testWorld.init("127.0.0.1", connection) catch |err| { + std.log.err("Encountered error while opening world: {s}", .{@errorName(err)}); + }; + main.game.world = &main.game.testWorld; + for(gui.openWindows.items) |openWindow| { + gui.closeWindow(openWindow) catch |err| { + std.log.err("Encountered error while opening world: {s}", .{@errorName(err)}); + }; + } + gui.openHud() catch |err| { + std.log.err("Encountered error while opening world: {s}", .{@errorName(err)}); + }; // while(Server.world == null) { // try { // Thread.sleep(10); diff --git a/src/main.zig b/src/main.zig index ebd53363..9a3b0557 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); pub const gui = @import("gui"); +pub const server = @import("server"); pub const assets = @import("assets.zig"); pub const blocks = @import("blocks.zig"); diff --git a/src/network.zig b/src/network.zig index d341b6d5..e6d4dfc2 100644 --- a/src/network.zig +++ b/src/network.zig @@ -602,14 +602,15 @@ pub const Protocols = struct { defer arrayList.deinit(); try arrayList.append(stepAssets); try utils.Compression.pack(dir, arrayList.writer()); - std.log.debug("{any}", .{arrayList.items}); try conn.sendImportant(id, arrayList.items); try conn.flush(); } // TODO: -// JsonObject jsonObject = new JsonObject(); -// ((User)conn).initPlayer(name); + conn.user.?.initPlayer(name); + const jsonObject = try JsonElement.initObject(main.threadAllocator); + defer jsonObject.free(main.threadAllocator); + // TODO: // jsonObject.put("player", ((User)conn).player.save()); // jsonObject.put("player_id", ((User)conn).player.id); // jsonObject.put("blockPalette", Server.world.blockPalette.save()); @@ -618,14 +619,12 @@ pub const Protocols = struct { // spawn.put("y", Server.world.spawn.y); // spawn.put("z", Server.world.spawn.z); // jsonObject.put("spawn", spawn); -// byte[] string = jsonObject.toString().getBytes(StandardCharsets.UTF_8); -// byte[] outData = new byte[string.length + 1]; -// outData[0] = STEP_SERVER_DATA; -// System.arraycopy(string, 0, outData, 1, string.length); -// state.put(conn, STEP_SERVER_DATA); -// conn.sendImportant(this, outData); -// state.remove(conn); // Handshake is done. -// conn.handShakeComplete = true; + const outData = try jsonObject.toStringEfficient(main.threadAllocator, &[1]u8{stepServerData}); + defer main.threadAllocator.free(outData); + try conn.sendImportant(id, outData); + conn.handShakeState = stepServerData; + conn.handShakeState = stepComplete; + // TODO: // synchronized(conn) { // Notify the waiting server thread. // conn.notifyAll(); // } @@ -1212,16 +1211,6 @@ pub const Protocols = struct { pub fn send(conn: *Connection, data: []const u8) !void { try conn.sendImportant(id, data); } -// TODO -// public void sendToClients(String msg) { -// Logger.log("chat", msg, "\033[0;32m"); -// synchronized(this) { -// for(User user : Server.users) { -// send(user, msg); -// } -// } -// } -//} }; }; @@ -1236,6 +1225,7 @@ pub const Connection = struct { var packetsResent: u32 = 0; manager: *ConnectionManager, + user: ?*main.server.User = null, gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined, // TODO: Removing this line causes a compiler crash. #15150 @@ -1563,7 +1553,7 @@ pub const Connection = struct { if(Protocols.list[protocol]) |prot| { try prot(self, data); } else { - std.log.warn("Received unknown important protocol width id {}", .{protocol}); + std.log.warn("Received unknown important protocol with id {}", .{protocol}); } } } diff --git a/src/server/server.zig b/src/server/server.zig new file mode 100644 index 00000000..9cc82b9e --- /dev/null +++ b/src/server/server.zig @@ -0,0 +1,228 @@ +const std = @import("std"); + +const main = @import("root"); +const network = main.network; +const Connection = network.Connection; +const ConnectionManager = network.ConnectionManager; +const utils = main.utils; +const vec = main.vec; +const Vec3d = vec.Vec3d; + + +pub const User = struct { + conn: *Connection, +//TODO: public Player player; + timeDifference: utils.TimeDifference = .{}, + interpolation: utils.GenericInterpolation(3) = undefined, + lastTime: i16 = undefined, + name: []const u8 = "", + renderDistance: u16 = undefined, + lodFactor: f32 = undefined, + receivedFirstEntityData: bool = false, + pos: [3]f64 = undefined, // TODO: Use position from te entity. + vel: [3]f64 = undefined, + // TODO: ipPort: []const u8, +// TODO: public Thread waitingThread; + + pub fn init(manager: *ConnectionManager, ipPort: []const u8) !*User { + const self = try main.globalAllocator.create(User); + self.* = User { + .conn = try Connection.init(manager, ipPort), + }; + self.conn.user = self; + self.interpolation.init(&self.pos, &self.vel); + // TODO: self.interpolation.init(&player.pos, &player.vel); + network.Protocols.handShake.serverSide(self.conn); + // TODO: +// synchronized(this) { +// waitingThread = Thread.currentThread(); +// this.wait(); +// waitingThread = null; +// } + return self; + } + + pub fn deinit(self: *User) void { + main.globalAllocator.destroy(self); + self.conn.deinit(); + } +// @Override +// public void disconnect() { +// super.disconnect(); +// Server.disconnect(this); +// } + + pub fn initPlayer(self: *User, name: []const u8) void { + self.name = name; + // TODO: +// assert(player == null); +// player = Server.world.findPlayer(this); +// interpolation.outPosition[0] = player.getPosition().x; +// interpolation.outPosition[1] = player.getPosition().y; +// interpolation.outPosition[2] = player.getPosition().z; +// interpolation.outVelocity[0] = player.vx; +// interpolation.outVelocity[1] = player.vy; +// interpolation.outVelocity[2] = player.vz; + } + + pub fn update(self: *User) void { + var time = @truncate(i16, std.time.milliTimestamp()) -% main.settings.entityLookback; + time -= self.timeDifference.difference.load(.Monotonic); + self.interpolation.update(time, self.lastTime); + // TODO: +// player.getPosition().x = interpolation.outPosition[0]; +// player.getPosition().y = interpolation.outPosition[1]; +// player.getPosition().z = interpolation.outPosition[2]; +// player.vx = interpolation.outVelocity[0]; +// player.vy = interpolation.outVelocity[1]; +// player.vz = interpolation.outVelocity[2]; + self.lastTime = time; + } + + pub fn receiveData(self: *User, data: []const u8) void { + const position: [3]f64 = .{ + @bitCast(f64, std.mem.readIntBig(u64, data[0..8])), + @bitCast(f64, std.mem.readIntBig(u64, data[8..16])), + @bitCast(f64, std.mem.readIntBig(u64, data[16..24])), + }; + const velocity: [3]f64 = .{ + @bitCast(f64, std.mem.readIntBig(u64, data[24..32])), + @bitCast(f64, std.mem.readIntBig(u64, data[32..40])), + @bitCast(f64, std.mem.readIntBig(u64, data[40..48])), + }; + const rotation: [3]f32 = .{ + @bitCast(f32, std.mem.readIntBig(u32, data[48..52])), + @bitCast(f32, std.mem.readIntBig(u32, data[52..56])), + @bitCast(f32, std.mem.readIntBig(u32, data[56..60])), + }; + _ = rotation; +// TODO: player.getRotation().set(rotation); + const time = std.mem.readIntBig(i16, data[60..62]); + self.timeDifference.addDataPoint(time); + self.interpolation.updatePosition(&position, &velocity, time); + } + // TODO (Command stuff): +// @Override +// public void feedback(String feedback) { +// Protocols.CHAT.send(this, "#ffff00"+feedback); +// } +}; + +const updatesPerSec: u32 = 20; +const updateNanoTime: u32 = 1000000000/20; +// TODO: +// public static ServerWorld world = null; +pub var users: std.ArrayList(*User) = undefined; + +pub var connectionManager: *ConnectionManager = undefined; + +var running: bool = false; +var lastTime: i128 = undefined; + +var mutex: std.Thread.Mutex = .{}; + +fn init() !void { + 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); + if(true) { // singleplayer // TODO: Configure this in the server settings. + const user = try User.init(connectionManager, "127.0.0.1:47650"); + try connect(user); + } +} + +fn deinit() void { + for(users.items) |user| { + user.deinit(); + } + users.clearAndFree(); + connectionManager.deinit(); + connectionManager = undefined; + // TODO: +// if(world != null) +// world.cleanup(); +// world = null; +} + +fn update() !void { +// TODO: world.update(); + mutex.lock(); + for(users.items) |user| { + user.update(); + } + mutex.unlock(); + // TODO: +// Entity[] entities = world.getEntities(); +// Protocols.ENTITY.sendToClients(entities, lastSentEntities, world.itemEntityManager); +// lastSentEntities = entities; +} + +pub fn start() !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(); + defer deinit(); + running = true; + while(running) { + const newTime = std.time.nanoTimestamp(); + if(newTime -% lastTime < updateNanoTime) { + std.time.sleep(@intCast(u64, lastTime +% updateNanoTime -% newTime)); + lastTime +%= updateNanoTime; + } else { + std.log.warn("The server is lagging behind by {d:.1} ms", .{@intToFloat(f32, newTime -% lastTime -% updateNanoTime)/1000000.0}); + lastTime = newTime; + } + try update(); + + } +} + +pub fn stop() void { + running = false; +} + +pub fn disconnect(user: *User) !void { + // TODO: world.forceSave(); + const message = try std.fmt.allocPrint(main.threadAllocator, "{s} #ffff00left", .{user.name}); + defer main.threadAllocator.free(message); + mutex.lock(); + defer mutex.unlock(); + try sendMessage(message); + + for(users.items, 0..) |other, i| { + if(other == user) { + _ = users.swapRemove(i); + break; + } + } +// TODO: world.removeEntity(user.player); +// TODO? users = usersList.toArray(); +} + +pub fn connect(user: *User) !void { + const message = try std.fmt.allocPrint(main.threadAllocator, "{s} #ffff00joined", .{user.name}); + defer main.threadAllocator.free(message); + mutex.lock(); + defer mutex.unlock(); + try sendMessage(message); + + try users.append(user); + // TODO: users = usersList.toArray(); +} + +// private Entity[] lastSentEntities = new Entity[0]; + +pub fn sendMessage(msg: []const u8) !void { + std.debug.assert(!mutex.tryLock()); // Mutex must be locked! + std.log.info("Chat: {s}", .{msg}); // TODO use color \033[0;32m + for(users.items) |user| { + try main.network.Protocols.chat.send(user.conn, msg); + } +} \ No newline at end of file