From 2248ba695aecd20e4998c30775975f19c031eb14 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Tue, 15 Sep 2026 21:22:00 +0200 Subject: [PATCH 1/3] Ignore bag undo operation with amount == 0, this causes unnecessary assertion failures --- src/sync.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sync.zig b/src/sync.zig index e9b74c6020..ca6d7f937a 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -621,6 +621,7 @@ pub const Command = struct { // MARK: Command info.dest.inv.update(); }, .moveToBag => |info| { + if (info.amount == 0) continue; const item = info.dest.peek(0).item; std.debug.assert(std.meta.eql(info.source.ref().item, item) or info.source.ref().item == .null); From c040d0e49d7447cd06b53d86041145ff5f7867c8 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Tue, 15 Sep 2026 21:25:13 +0200 Subject: [PATCH 2/3] Properly synchronize the client side sync commands --- src/game.zig | 5 +++ src/network/protocols.zig | 6 +-- src/sync.zig | 85 +++++++++++++++++++++++++-------------- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/game.zig b/src/game.zig index e59c0a5905..ada546c5b6 100644 --- a/src/game.zig +++ b/src/game.zig @@ -627,6 +627,11 @@ pub fn update(deltaTime: f64) void { // MARK: update() if (world.?.shouldRestart.load(.acquire)) { restart(); } + main.sync.client.update() catch |err| { + std.log.err("Got error while processing server sync commands: {s}. Disconnecting", .{@errorName(err)}); + main.exitToMenu(); + return; + }; physics.calculateVolumeProperties(.client, &Player.volumeProperties, Player.super.pos, Player.outerBoundingBox, physics.playerAirTerminalVelocity); if (Player.isFlying.load(.monotonic)) { diff --git a/src/network/protocols.zig b/src/network/protocols.zig index fa59c9474d..5dc707f579 100644 --- a/src/network/protocols.zig +++ b/src/network/protocols.zig @@ -959,11 +959,11 @@ pub const inventory = struct { // MARK: inventory fn clientReceive(_: *Connection, reader: *utils.BinaryReader) !void { const typ = try reader.readInt(u8); if (typ == 0xff) { // Confirmation - try main.sync.client.receiveConfirmation(reader); + main.sync.client.receiveSyncOperation(.init(.confirmation, reader.remaining)); } else if (typ == 0xfe) { // Failure - main.sync.client.receiveFailure(); + main.sync.client.receiveSyncOperation(.init(.failure, &.{})); } else { - try main.sync.client.receiveSyncOperation(reader); + main.sync.client.receiveSyncOperation(.init(.sync, reader.remaining)); } } fn serverReceive(conn: *Connection, reader: *utils.BinaryReader) !void { diff --git a/src/sync.zig b/src/sync.zig index ca6d7f937a..d0c98f5e22 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -27,6 +27,22 @@ pub const Side = enum { client, server }; pub const client = struct { // MARK: client pub var mutex: main.utils.Mutex = .{}; var commands: utils.CircularBufferQueue(Command) = undefined; + var syncCommands: main.ListManaged(ClientSyncOperation) = .init(main.globalAllocator); + const ClientSyncOperation = struct { + const Type = enum { confirmation, failure, sync }; + typ: Type, + data: []const u8, + + pub fn init(typ: Type, data: []const u8) ClientSyncOperation { + return .{ + .typ = typ, + .data = main.globalAllocator.dupe(u8, data), + }; + } + fn deinit(self: ClientSyncOperation) void { + main.globalAllocator.free(self.data); + } + }; pub fn init() void { commands = utils.CircularBufferQueue(Command).init(main.globalAllocator, 256); @@ -35,6 +51,7 @@ pub const client = struct { // MARK: client pub fn deinit() void { reset(); commands.deinit(); + syncCommands.deinit(); } pub fn reset() void { @@ -45,6 +62,9 @@ pub const client = struct { // MARK: client std.log.err("Got error while cleaning remaining inventory commands: {s}", .{@errorName(err)}); }; } + while (syncCommands.popOrNull()) |sync| { + sync.deinit(); + } mutex.unlock(); } @@ -62,20 +82,17 @@ pub const client = struct { // MARK: client commands.pushBack(cmd); } - pub fn receiveConfirmation(reader: *BinaryReader) !void { + pub fn receiveSyncOperation(sync: ClientSyncOperation) void { mutex.lock(); defer mutex.unlock(); - if (commands.popFront()) |cmd| { - try cmd.finalize(main.globalAllocator, .client, reader); - } else { - std.log.err("Received unexpected confirmation sync. Disconnecting", .{}); - return error.Invalid; - } + syncCommands.append(sync); } - pub fn receiveFailure() void { + pub fn update() !void { mutex.lock(); defer mutex.unlock(); + if (syncCommands.items.len == 0) return; + var tempData: main.List(Command) = .empty; defer tempData.deinit(main.stackAllocator); while (commands.popBack()) |_cmd| { @@ -83,31 +100,37 @@ pub const client = struct { // MARK: client cmd.undo(); tempData.append(main.stackAllocator, cmd); } - if (tempData.popOrNull()) |_cmd| { - var cmd = _cmd; - var reader = BinaryReader.init(&.{}); - cmd.finalize(main.globalAllocator, .client, &reader) catch |err| { - std.log.err("Got error while cleaning rejected inventory command: {s}", .{@errorName(err)}); - }; - } - while (tempData.popOrNull()) |_cmd| { - var cmd = _cmd; - cmd.do(main.globalAllocator, .client, null, main.game.Player.gamemode.raw) catch unreachable; - commands.pushBack(cmd); - } - } - pub fn receiveSyncOperation(reader: *BinaryReader) !void { - mutex.lock(); - defer mutex.unlock(); - var tempData: main.List(Command) = .empty; - defer tempData.deinit(main.stackAllocator); - while (commands.popBack()) |_cmd| { - var cmd = _cmd; - cmd.undo(); - tempData.append(main.stackAllocator, cmd); + for (syncCommands.items) |sync| { + defer sync.deinit(); + var reader = BinaryReader.init(sync.data); + + switch(sync.typ) { + .confirmation => { + if (tempData.popOrNull()) |_cmd| { + var cmd = _cmd; + cmd.do(main.globalAllocator, .client, null, main.game.Player.gamemode.raw) catch unreachable; + try cmd.finalize(main.globalAllocator, .client, &reader); + } else { + std.log.err("Received unexpected confirmation sync. Disconnecting", .{}); + return error.Invalid; + } + }, + .failure => { + if (tempData.popOrNull()) |cmd| { + try cmd.finalize(main.globalAllocator, .client, &reader); + } else { + std.log.err("Received unexpected failure sync. Disconnecting", .{}); + return error.Invalid; + } + }, + .sync => { + try Command.SyncOperation.executeFromData(&reader); + }, + } } - try Command.SyncOperation.executeFromData(reader); + syncCommands.clearRetainingCapacity(); + while (tempData.popOrNull()) |_cmd| { var cmd = _cmd; cmd.do(main.globalAllocator, .client, null, main.game.Player.gamemode.raw) catch unreachable; From 8a8e287784eb11605dc39bc2ff7a52fa93ecdbe5 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Tue, 15 Sep 2026 21:34:11 +0200 Subject: [PATCH 3/3] format --- src/sync.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync.zig b/src/sync.zig index d0c98f5e22..b1283cd1fb 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -105,7 +105,7 @@ pub const client = struct { // MARK: client defer sync.deinit(); var reader = BinaryReader.init(sync.data); - switch(sync.typ) { + switch (sync.typ) { .confirmation => { if (tempData.popOrNull()) |_cmd| { var cmd = _cmd;