From 29f06de52f636b6899c43a3cc9a318fbc7accf07 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sat, 26 Apr 2025 15:59:59 +0200 Subject: [PATCH] Implement some basic lossy compression for entity and item network data progress towards #1327 --- src/entity.zig | 32 ++++++++------ src/itemdrop.zig | 31 ++++++++----- src/network.zig | 100 +++++++++++++++++++++++++++++++++--------- src/server/server.zig | 18 ++++---- 4 files changed, 127 insertions(+), 54 deletions(-) diff --git a/src/entity.zig b/src/entity.zig index 02f9f599..de186d16 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -18,6 +18,13 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; const BinaryReader = main.utils.BinaryReader; +pub const EntityNetworkData = struct { + id: u32, + pos: Vec3d, + vel: Vec3d, + rot: Vec3f, +}; + pub const ClientEntity = struct { interpolatedValues: utils.GenericInterpolation(6) = undefined, _interpolationPos: [6]f64 = undefined, @@ -228,31 +235,30 @@ pub const ClientEntityManager = struct { } } - pub fn serverUpdate(time: i16, reader: *BinaryReader) !void { + pub fn serverUpdate(time: i16, entityData: []EntityNetworkData) void { mutex.lock(); defer mutex.unlock(); timeDifference.addDataPoint(time); - while(reader.remaining.len != 0) { - const id = try reader.readInt(u32); + for(entityData) |data| { const pos = [_]f64{ - try reader.readFloat(f64), - try reader.readFloat(f64), - try reader.readFloat(f64), - @floatCast(try reader.readFloat(f32)), - @floatCast(try reader.readFloat(f32)), - @floatCast(try reader.readFloat(f32)), + data.pos[0], + data.pos[1], + data.pos[2], + @floatCast(data.rot[0]), + @floatCast(data.rot[1]), + @floatCast(data.rot[2]), }; const vel = [_]f64{ - try reader.readFloat(f64), - try reader.readFloat(f64), - try reader.readFloat(f64), + data.vel[0], + data.vel[1], + data.vel[2], 0, 0, 0, }; for(entities.items()) |*ent| { - if(ent.id == id) { + if(ent.id == data.id) { ent.updatePosition(&pos, &vel, time); break; } diff --git a/src/itemdrop.zig b/src/itemdrop.zig index 48c73a4b..f5cd8715 100644 --- a/src/itemdrop.zig +++ b/src/itemdrop.zig @@ -35,6 +35,12 @@ const ItemDrop = struct { // MARK: ItemDrop reverseIndex: u16, }; +pub const ItemDropNetworkData = struct { + index: u16, + pos: Vec3d, + vel: Vec3d, +}; + pub const ItemDropManager = struct { // MARK: ItemDropManager /// Half the side length of all item entities hitboxes as a cube. pub const radius: f64 = 0.1; @@ -116,14 +122,16 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager } } - pub fn getPositionAndVelocityData(self: *ItemDropManager, allocator: NeverFailingAllocator) []u8 { - var writer = utils.BinaryWriter.initCapacity(allocator, self.size*50); - for(self.indices[0..self.size]) |i| { - writer.writeInt(u16, i); - writer.writeVec(Vec3d, self.list.items(.pos)[i]); - writer.writeVec(Vec3d, self.list.items(.vel)[i]); + pub fn getPositionAndVelocityData(self: *ItemDropManager, allocator: NeverFailingAllocator) []ItemDropNetworkData { + const result = allocator.alloc(ItemDropNetworkData, self.size); + for(self.indices[0..self.size], result) |i, *res| { + res.* = .{ + .index = i, + .pos = self.list.items(.pos)[i], + .vel = self.list.items(.vel)[i], + }; } - return writer.data.toOwnedSlice(); + return result; } pub fn getInitialList(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement { @@ -458,14 +466,13 @@ pub const ClientItemDropManager = struct { // MARK: ClientItemDropManager self.super.deinit(); } - pub fn readPosition(self: *ClientItemDropManager, reader: *BinaryReader, time: i16) !void { + pub fn readPosition(self: *ClientItemDropManager, time: i16, itemData: []ItemDropNetworkData) void { self.timeDifference.addDataPoint(time); var pos: [ItemDropManager.maxCapacity]Vec3d = undefined; var vel: [ItemDropManager.maxCapacity]Vec3d = undefined; - while(reader.remaining.len != 0) { - const i = try reader.readInt(u16); - pos[i] = try reader.readVec(Vec3d); - vel[i] = try reader.readVec(Vec3d); + for(itemData) |data| { + pos[data.index] = data.pos; + vel[data.index] = data.vel; } mutex.lock(); defer mutex.unlock(); diff --git a/src/network.zig b/src/network.zig index a7255ed1..918a55f2 100644 --- a/src/network.zig +++ b/src/network.zig @@ -850,35 +850,93 @@ pub const Protocols = struct { pub const asynchronous = false; const type_entity: u8 = 0; const type_item: u8 = 1; + const Type = enum(u8) { + noVelocityEntity = 0, + f16VelocityEntity = 1, + f32VelocityEntity = 2, + noVelocityItem = 3, + f16VelocityItem = 4, + f32VelocityItem = 5, + }; fn receive(conn: *Connection, reader: *utils.BinaryReader) !void { + if(conn.isServerSide()) return error.InvalidSide; if(conn.manager.world) |world| { - const typ = try reader.readInt(u8); const time = try reader.readInt(i16); - if(typ == type_entity) { - try main.entity.ClientEntityManager.serverUpdate(time, reader); - } else if(typ == type_item) { - try world.itemDrops.readPosition(reader, time); + const playerPos = try reader.readVec(Vec3d); + var entityData: main.List(main.entity.EntityNetworkData) = .init(main.stackAllocator); + defer entityData.deinit(); + var itemData: main.List(main.itemdrop.ItemDropNetworkData) = .init(main.stackAllocator); + defer itemData.deinit(); + while(reader.remaining.len != 0) { + const typ = try reader.readEnum(Type); + switch(typ) { + .noVelocityEntity, .f16VelocityEntity, .f32VelocityEntity => { + entityData.append(.{ + .vel = switch(typ) { + .noVelocityEntity => @splat(0), + .f16VelocityEntity => @floatCast(try reader.readVec(@Vector(3, f16))), + .f32VelocityEntity => @floatCast(try reader.readVec(@Vector(3, f32))), + else => unreachable, + }, + .id = try reader.readInt(u32), + .pos = playerPos + try reader.readVec(Vec3f), + .rot = try reader.readVec(Vec3f), + }); + }, + .noVelocityItem, .f16VelocityItem, .f32VelocityItem => { + itemData.append(.{ + .vel = switch(typ) { + .noVelocityItem => @splat(0), + .f16VelocityItem => @floatCast(try reader.readVec(@Vector(3, f16))), + .f32VelocityItem => @floatCast(try reader.readVec(Vec3f)), + else => unreachable, + }, + .index = try reader.readInt(u16), + .pos = playerPos + try reader.readVec(Vec3f), + }); + }, + } } + main.entity.ClientEntityManager.serverUpdate(time, entityData.items); + world.itemDrops.readPosition(time, itemData.items); } } - pub fn send(conn: *Connection, entityData: []const u8, itemData: []const u8) void { - if(entityData.len != 0) { - var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, entityData.len + 3); - defer writer.deinit(); - writer.writeInt(u8, type_entity); - writer.writeInt(i16, @truncate(std.time.milliTimestamp())); - writer.writeSlice(entityData); - conn.send(.lossy, id, writer.data.items); - } + pub fn send(conn: *Connection, playerPos: Vec3d, entityData: []main.entity.EntityNetworkData, itemData: []main.itemdrop.ItemDropNetworkData) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); - if(itemData.len != 0) { - var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, itemData.len + 3); - defer writer.deinit(); - writer.writeInt(u8, type_item); - writer.writeInt(i16, @truncate(std.time.milliTimestamp())); - writer.writeSlice(itemData); - conn.send(.lossy, id, writer.data.items); + writer.writeInt(i16, @truncate(std.time.milliTimestamp())); + writer.writeVec(Vec3d, playerPos); + for(entityData) |data| { + const velocityMagnitudeSqr = vec.lengthSquare(data.vel); + if(velocityMagnitudeSqr < 1e-6*1e-6) { + writer.writeEnum(Type, .noVelocityEntity); + } else if(velocityMagnitudeSqr > 1000*1000) { + writer.writeEnum(Type, .f32VelocityEntity); + writer.writeVec(Vec3f, @floatCast(data.vel)); + } else { + writer.writeEnum(Type, .f16VelocityEntity); + writer.writeVec(@Vector(3, f16), @floatCast(data.vel)); + } + writer.writeInt(u32, data.id); + writer.writeVec(Vec3f, @floatCast(data.pos - playerPos)); + writer.writeVec(Vec3f, data.rot); } + for(itemData) |data| { + const velocityMagnitudeSqr = vec.lengthSquare(data.vel); + if(velocityMagnitudeSqr < 1e-6*1e-6) { + writer.writeEnum(Type, .noVelocityItem); + } else if(velocityMagnitudeSqr > 1000*1000) { + writer.writeEnum(Type, .f32VelocityItem); + writer.writeVec(Vec3f, @floatCast(data.vel)); + } else { + writer.writeEnum(Type, .f16VelocityItem); + writer.writeVec(@Vector(3, f16), @floatCast(data.vel)); + } + writer.writeInt(u16, data.index); + writer.writeVec(Vec3f, @floatCast(data.pos - playerPos)); + } + conn.send(.lossy, id, writer.data.items); } }; pub const blockUpdate = struct { diff --git a/src/server/server.zig b/src/server/server.zig index c7e5c2e7..6252e845 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -387,21 +387,23 @@ fn update() void { // MARK: update() } // Send the entity data: - var writer = BinaryWriter.initCapacity(main.stackAllocator, (4 + 24 + 12 + 24)*userList.len); - defer writer.deinit(); - const itemData = world.?.itemDropManager.getPositionAndVelocityData(main.stackAllocator); defer main.stackAllocator.free(itemData); + var entityData: main.List(main.entity.EntityNetworkData) = .init(main.stackAllocator); + defer entityData.deinit(); + for(userList) |user| { const id = user.id; // TODO - writer.writeInt(u32, id); - writer.writeVec(Vec3d, user.player.pos); - writer.writeVec(Vec3f, user.player.rot); - writer.writeVec(Vec3d, user.player.vel); + entityData.append(.{ + .id = id, + .pos = user.player.pos, + .vel = user.player.vel, + .rot = user.player.rot, + }); } for(userList) |user| { - main.network.Protocols.entityPosition.send(user.conn, writer.data.items, itemData); + main.network.Protocols.entityPosition.send(user.conn, user.player.pos, entityData.items, itemData); } while(userDeinitList.dequeue()) |user| {