From 5a79ae71c081fe91f0be3e985c50e6f33aa57f53 Mon Sep 17 00:00:00 2001 From: Vicente Ferrari Smith Date: Sun, 8 Feb 2026 15:22:31 +0100 Subject: [PATCH] . --- .vscode/tasks.json | 2 +- build.zig | 41 ++++--- src/client/client.zig | 3 +- src/client/main.zig | 90 +++++++++++---- src/server/chunk.zig | 74 +++++------- src/server/main.zig | 138 ++++++++-------------- src/shared/bits.zig | 212 +++++++++++++++++++++++++++++++--- src/shared/chunk.zig | 131 +++++++++++++-------- src/shared/entity.zig | 80 ++++++++++--- src/shared/protocol.zig | 129 --------------------- src/shared/shared.zig | 15 ++- vendor/freetype/build.zig.zon | 1 + 12 files changed, 531 insertions(+), 385 deletions(-) delete mode 100644 src/shared/protocol.zig diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cedaa78..a10386b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -17,7 +17,7 @@ "focus": false, "panel": "shared", "showReuseMessage": true, - "clear": false + "clear": true, }, "problemMatcher": [], "group": { diff --git a/build.zig b/build.zig index 7540da0..5ad5aa8 100644 --- a/build.zig +++ b/build.zig @@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void { .imports = &.{ .{ .name = "shared", - .module = shared + .module = shared, }, }, }), @@ -46,7 +46,10 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, .imports = &.{ - .{ .name = "shared", .module = shared }, + .{ + .name = "shared", + .module = shared, + }, }, }), .use_llvm = true, @@ -62,17 +65,20 @@ pub fn build(b: *std.Build) void { .enable_tracy = true, .enable_fibers = false, .on_demand = false, - } + }, ); client.root_module.addImport("tracy", tracy.module("tracy")); } // freetype { - const freetype = b.dependency("freetype", .{ - .target = target, - .optimize = optimize, - }); + const freetype = b.dependency( + "freetype", + .{ + .target = target, + .optimize = optimize, + } + ); client.root_module.addImport("freetype", freetype.module("freetype")); } @@ -170,29 +176,32 @@ pub fn build(b: *std.Build) void { b.getInstallStep().dependOn(&assets.step); - const run_step = b.step("run", "Run the app"); + const run_client_step = b.step("run-client", "Run the client"); + const run_server_step = b.step("run-server", "Run the server"); const run_cmd_client = b.addRunArtifact(client); const run_cmd_server = b.addRunArtifact(server); - run_step.dependOn(&run_cmd_client.step); - run_step.dependOn(&run_cmd_server.step); + run_client_step.dependOn(&run_cmd_client.step); + run_server_step.dependOn(&run_cmd_server.step); run_cmd_client.step.dependOn(b.getInstallStep()); run_cmd_server.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd_client.addArgs(args); + run_cmd_server.addArgs(args); } // Creates an executable that will run `test` blocks from the provided module. // Here `mod` needs to define a target, which is why earlier we made sure to // set the releative field. - // const mod_tests = b.addTest(.{ - // .root_module = mod, - // }); + const shared_tests = b.addTest(.{ + .root_module = shared, + .use_llvm = true, + }); // A run step that will run the test executable. - // const run_mod_tests = b.addRunArtifact(mod_tests); + const run_shared_tests = b.addRunArtifact(shared_tests); // Creates an executable that will run `test` blocks from the executable's // root module. Note that test executables only test one module at a time, @@ -207,8 +216,8 @@ pub fn build(b: *std.Build) void { // A top level step for running all tests. dependOn can be called multiple // times and since the two run steps do not depend on one another, this will // make the two of them run in parallel. - // const test_step = b.step("test", "Run tests"); - // test_step.dependOn(&run_mod_tests.step); + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_shared_tests.step); // test_step.dependOn(&run_exe_tests.step); // Just like flags, top level steps are also listed in the `--help` menu. diff --git a/src/client/client.zig b/src/client/client.zig index 580a856..d3c5da9 100644 --- a/src/client/client.zig +++ b/src/client/client.zig @@ -1,4 +1,5 @@ -const std = @import("std"); +const std = @import("std"); +const shared = @import("shared"); const ConnectState = union(enum) { disconnected, diff --git a/src/client/main.zig b/src/client/main.zig index d3e0801..dae49ff 100644 --- a/src/client/main.zig +++ b/src/client/main.zig @@ -14,8 +14,9 @@ const client = @import("client.zig"); const entity = @import("entity.zig"); const misc = @import("misc.zig"); const font = @import("font.zig"); +const chunk = @import("chunk.zig"); -const dt : f32 = 1.0 / 200.0; +const dt : f32 = 1.0 / 240.0; var t : f32 = 0; var gt : f32 = 0; var accumulator : f32 = 0; @@ -29,6 +30,8 @@ var running: bool = true; var dbg_allocator = std.heap.DebugAllocator(.{}){}; +var the_chunk : shared.chunk.Chunk = undefined; + pub fn main() !void { const tracy_zone = tracy.ZoneNC(@src(), "main", 0x00_ff_00_00); defer tracy_zone.End(); @@ -83,14 +86,14 @@ pub fn main() !void { const host = enet.enet_host_create(null, 32, 2, 0, 0); defer enet.enet_host_destroy(host); - var address = enet.ENetAddress{ .port = shared.protocol.SERVER_PORT }; + var address = enet.ENetAddress{ .port = shared.protocol_v1.SERVER_PORT }; _ = enet.enet_address_set_host(&address, "localhost"); const peer = enet.enet_host_connect(host, &address, 2, 0); defer enet.enet_peer_reset(peer); - var the_chunk = try shared.chunk.initChunk(allocator); - defer shared.chunk.deinitChunk(&the_chunk, allocator); + the_chunk = try shared.chunk.Chunk.init(allocator); + defer the_chunk.deinit(allocator); // const camera = rl.Camera{ // .fovy = 45, @@ -135,13 +138,14 @@ pub fn main() !void { switch (event.type) { enet.ENET_EVENT_TYPE_CONNECT => { std.log.info("A new client connected from {d}:{d}.", .{ - event.peer.*.address.host, - event.peer.*.address.port}); + event.peer.*.address.host, + event.peer.*.address.port + }); event.peer.*.data = @constCast(@ptrCast("Client information")); }, enet.ENET_EVENT_TYPE_RECEIVE => { - try on_packet(event.packet, event.peer, event.channelID); + try on_packet(allocator, event.packet, event.peer, event.channelID); enet.enet_packet_destroy(event.packet); }, @@ -163,6 +167,7 @@ pub fn main() !void { // rl.updateMusicStream(music); while (accumulator > dt * k) { + the_chunk.update(dt * k); accumulator -= dt * k; gt += dt * k; } @@ -320,7 +325,7 @@ pub fn main() !void { } } -fn on_packet(packet: *enet.ENetPacket, peer: *enet.ENetPeer, channelID: i32) !void { +fn on_packet(allocator: std.mem.Allocator, packet: *enet.ENetPacket, peer: *enet.ENetPeer, channelID: i32) !void { // std.log.info("A packet of length {d} containing {s} was received from {s} on channel {d}.", .{ // packet.*.dataLength, // packet.*.data, @@ -331,27 +336,72 @@ fn on_packet(packet: *enet.ENetPacket, peer: *enet.ENetPeer, channelID: i32) !vo _ = channelID; const bytes: []const u8 = packet.*.data[0 .. packet.*.dataLength]; - - std.log.info("{d} bytes: {s}", .{bytes.len, bytes}); - // var buffer2: [4096]u8 = undefined; - // var fixed2 = std.io.Writer.fixed(&buffer2); + var reader = shared.bits.BitReader.init(bytes); - // var inspector = bufzilla.Inspect(.{}).init(encoded, &fixed2, .{}); - // try inspector.inspect(); + const msg : shared.protocol_v1.Message = try reader.deserialize(allocator, shared.protocol_v1.Message); - // std.log.info("{s}\n", .{fixed2.buffered()}); + try on_message(allocator, msg); +} - // var r = bufzilla.Reader(.{}).init(encoded); +fn on_message(allocator: std.mem.Allocator, msg: shared.protocol_v1.Message) !void { + switch (msg) { + .spawn_entity => |se| { try on_spawn_entity(allocator, se); }, + .update_entity => |ue| { on_update_entity(ue); }, + .despawn_entity => |de| { on_despawn_entity(allocator, de); }, + } +} - // Read values sequentially +fn on_spawn_entity(allocator: std.mem.Allocator, se: shared.protocol_v1.SpawnEntity) !void { + // _ = allocator; _ = se; + switch (se) { + .soldier => |s| { + const soldier = shared.entity.Soldier{ + .id = s.id, + .hp = s.hp, + .pos = s.pos, + .vel = s.vel, + }; + _ = try chunk.spawn(&the_chunk, allocator, shared.entity.Soldier, soldier); + }, + .alien => |a| { + const alien = shared.entity.Alien{ + .id = a.id, + .hp = a.hp, + .pos = a.pos, + }; + _ = try chunk.spawn(&the_chunk, allocator, shared.entity.Alien, alien); + }, + } +} - // const e = try shared.protocol.Soldier_v1.decode(&r); - // _ = e; +fn on_update_entity(ue: shared.protocol_v1.UpdateEntity) void { + switch (ue) { + .soldier => |s| { + if (chunk.findT(&the_chunk, shared.entity.Soldier, s.id)) |soldier| { + soldier.hp = s.hp; + soldier.pos = s.pos; + soldier.vel = s.vel; + } + }, + .alien => |a| { + if (chunk.findT(&the_chunk, shared.entity.Alien, a.id)) |alien| { + alien.hp = a.hp; + alien.pos = a.pos; + } + }, + } +} + +fn on_despawn_entity(allocator: std.mem.Allocator, de: shared.protocol_v1.DespawnEntity) void { + switch (de) { + .soldier => |s| { _ = chunk.despawn(&the_chunk, allocator, s.id); }, + .alien => |a| { _ = chunk.despawn(&the_chunk, allocator, a.id); }, + } } fn connect() !void { - // const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol.SERVER_PORT); + // const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol_v1.SERVER_PORT); // connection = try std.net.tcpConnectToAddress(address); diff --git a/src/server/chunk.zig b/src/server/chunk.zig index e7f870f..6e2ee85 100644 --- a/src/server/chunk.zig +++ b/src/server/chunk.zig @@ -5,66 +5,52 @@ const shared = @import("shared"); const server = @import("server.zig"); const enet = @import("c.zig").enet; -pub fn spawn(allocator: std.mem.Allocator, host: *enet.ENetHost, chunk: *shared.chunk.Chunk, comptime T: type, value: T) !void { +pub fn spawn(self: *shared.chunk.Chunk, allocator: std.mem.Allocator, host: *enet.ENetHost, comptime T: type, value: T) !shared.entity.id { std.debug.assert(value.id == shared.entity.INVALID_ID); const id = server.next_entity_id; server.next_entity_id += 1; - var entity = value; - entity.id = id; + const ptr = try allocator.create(T); + errdefer allocator.destroy(ptr); + + ptr.* = value; + ptr.id = id; + + const ref = shared.entity.makeRef(T, ptr); + + try self.entities.append(allocator, ref); + + _ = host; - inline for (@typeInfo(shared.chunk.Chunk).@"struct".fields) |field| { - if (field.type == std.ArrayList(T)) { - try @field(chunk, field.name).append(allocator, entity); - break; - } - } // std.log.info("{}", .{shared.protocol.makeSpawnMessage(T, entity)}); // serialize entity - var buffer: [128]u8 = undefined; - var fixed = std.io.Writer.fixed(&buffer); - // const msg = shared.protocol.makeSpawnMessage(T, entity); - // var writer = bufzilla.Writer.init(&fixed); - // try writer.writeAny(msg); + // const data = fixed.buffered(); + // const packet = enet.enet_packet_create(data.ptr, data.len + 1, enet.ENET_PACKET_FLAG_RELIABLE); - // std.log.info("{s}", .{fixed.buffered()}); + // enet.enet_host_broadcast(host, 0, packet); - // var buffer2: [4096]u8 = undefined; - // var fixed2 = std.io.Writer.fixed(&buffer2); - - // var inspector = bufzilla.Inspect(.{}).init(fixed.buffered(), &fixed2, .{}); - // try inspector.inspect(); - - // std.log.info("{}", .{msg}); - - // std.debug.print("{s}\n", .{fixed2.buffered()}); - - const data = fixed.buffered(); - const packet = enet.enet_packet_create(data.ptr, data.len + 1, enet.ENET_PACKET_FLAG_RELIABLE); - - enet.enet_host_broadcast(host, 0, packet); - - // const encoded = fixed.buffered(); - - // const packet = try znet.Packet.init(encoded, 0, .reliable); - // var iterator = server.host.iterPeers(); - - // while (iterator.next()) |peer| { - // if (peer.state() == .connected) { - // try peer.send(packet); - // } - // } + return ptr.id; } -pub fn broadcastChanges(chunk: *shared.chunk.Chunk, host: *enet.ENetHost) !void { - _ = chunk; - const data = "packet"; - const packet = enet.enet_packet_create(data, data.len + 1, enet.ENET_PACKET_FLAG_RELIABLE); +pub fn broadcastChanges(chunk: *shared.chunk.Chunk, allocator: std.mem.Allocator, host: *enet.ENetHost) !void { + var aw = try std.ArrayList(u8).initCapacity(allocator, 128); + defer aw.deinit(allocator); + + var writer = shared.bits.BitWriter.init(&aw); + + for (chunk.entities.items) |e| { + const msg = shared.protocol_v1.makeUpdateMessage(e); + try writer.serialize(allocator, msg); + } + + const encoded = writer.written(); + + const packet = enet.enet_packet_create(encoded.ptr, encoded.len, enet.ENET_PACKET_FLAG_RELIABLE); enet.enet_host_broadcast(host, 0, packet); } diff --git a/src/server/main.zig b/src/server/main.zig index eb60a89..c93b4ec 100644 --- a/src/server/main.zig +++ b/src/server/main.zig @@ -8,17 +8,18 @@ const shared = @import("shared"); const chunk = @import("chunk.zig"); const server = @import("server.zig"); -const dt : f32 = 1.0 / 200.0; +const dt : f32 = 1.0 / 240.0; var t : f32 = 0; var gt : f32 = 0; -var accumulator : f32 = 0; var k : f32 = 1.0; var frame : i32 = 0; -var send_accumulator : f32 = 0; +const frame_dt_ns: i128 = std.time.ns_per_s / 10; var dbg_allocator = std.heap.DebugAllocator(.{}){}; +const log = std.log.scoped(.server); + pub fn main() !void { const allocator = dbg_allocator.allocator(); @@ -33,46 +34,52 @@ pub fn main() !void { _ = try stdout.write("Hello, Server!\n"); if (enet.enet_initialize() != 0) { - std.log.info("Failed to load ENet", .{}); + log.info("Failed to load ENet", .{}); return; } defer enet.enet_deinitialize(); - const address = enet.ENetAddress{ .host = enet.ENET_HOST_ANY, .port = shared.protocol.SERVER_PORT }; + const address = enet.ENetAddress{ .host = enet.ENET_HOST_ANY, .port = shared.protocol_v1.SERVER_PORT }; const host = enet.enet_host_create(&address, 32, 2, 0, 0); if (host == null) { @panic("host is null"); } defer enet.enet_host_destroy(host); - var the_chunk = try shared.chunk.initChunk(allocator); - defer shared.chunk.deinitChunk(&the_chunk, allocator); + var the_chunk = try shared.chunk.Chunk.init(allocator); + defer the_chunk.deinit(allocator); - try chunk.spawn( + _ = try chunk.spawn( + &the_chunk, allocator, host, - &the_chunk, shared.entity.Soldier, .{ .hp = 10, .pos = zm.f32x4(5.0, 0, 0, 0), - .vel = zm.f32x4(0, 0, 0, 0), + .vel = zm.f32x4(0, 0, 0, 0), } ); - var old_time = std.time.nanoTimestamp(); + var last_time: i128 = std.time.nanoTimestamp(); + var next_frame_deadline: i128 = last_time + frame_dt_ns; + + var accumulator: f32 = 0; while (true) { - const new_time = std.time.nanoTimestamp(); - var frame_time : f32 = @as(f32, @floatFromInt(new_time - old_time)) / 1_000_000_000.0; - old_time = new_time; + const now = std.time.nanoTimestamp(); + var delta_ns = now - last_time; + last_time = now; - if (frame_time > 0.25) - frame_time = 0.25; + if (delta_ns > std.time.ns_per_s / 4) + delta_ns = std.time.ns_per_s / 4; - t += frame_time; - accumulator += frame_time * k; - send_accumulator += frame_time; + const delta_s: f32 = @floatCast(@as(f64, @floatFromInt(delta_ns)) / 1e9); + + t += delta_s; + accumulator += delta_s * k; + + log.info("Starting a the frame {} at time {}", .{frame, t}); var event = enet.ENetEvent{}; while (enet.enet_host_service(host, &event, 0) > 0) { @@ -81,10 +88,10 @@ pub fn main() !void { try on_connect(allocator, host, event.peer, &the_chunk); }, enet.ENET_EVENT_TYPE_RECEIVE => { - std.log.info("receive", .{}); + log.info("receive", .{}); }, enet.ENET_EVENT_TYPE_DISCONNECT => { - std.log.info("disconnect", .{}); + log.info("disconnect", .{}); }, else => {}, } @@ -97,7 +104,7 @@ pub fn main() !void { // var reader = connection.stream.reader(&recv_buffer); // const line = try reader.interface().takeDelimiterExclusive('\n'); - // std.log.info("Received: {s}", .{line}); + // log.info("Received: {s}", .{line}); // var send_buffer: [4096]u8 = undefined; // var writer = connection.stream.writer(&send_buffer); @@ -115,18 +122,29 @@ pub fn main() !void { // }, w); while (accumulator > dt * k) { - shared.chunk.updateChunk(&the_chunk); + the_chunk.update(dt * k); accumulator -= dt * k; gt += dt * k; } - if (send_accumulator > 1) { - // try chunk.broadcastChanges(&the_chunk, host); + try chunk.broadcastChanges(&the_chunk, allocator, host); - send_accumulator = 0; + const spin_ns: i128 = 100_000; // 0.1 ms + + while (true) { + const _t = std.time.nanoTimestamp(); + const remaining = next_frame_deadline - _t; + + if (remaining <= 0) + break; + + if (remaining > spin_ns) + std.Thread.sleep(@intCast(remaining - spin_ns)); } + next_frame_deadline += frame_dt_ns; + frame += 1; } } @@ -137,74 +155,18 @@ fn on_connect(allocator: std.mem.Allocator, host: *enet.ENetHost, peer: *enet.EN _ = peer; - // var w = bufzilla.Writer.init(&aw.writer); - var writer = shared.bits.BitWriter.init(&aw); - // Write 3 bits (101) - try writer.write(allocator, @as(u3, 0x5)); - // Write 1 bit (true = 1) - try writer.write(allocator, true); - // Write 4 bits (0000) - try writer.write(allocator, @as(u3, 0x1)); - - try writer.write(allocator, @as(u4, 0xE)); - - try writer.write(allocator, @as(u32, 0xFFFFFFFA)); - - var reader = shared.bits.BitReader.init(writer.written()); - - const _1 : u3 = reader.read(u3); - const _2 : bool = reader.read(bool); - const _3 : u3 = reader.read(u3); - const _4 : u4 = reader.read(u4); - const _5 : u32 = reader.read(u32); - - std.log.info("_1: {}", .{_1}); - std.log.info("_1: {}", .{_2}); - std.log.info("_1: {}", .{_3}); - std.log.info("_1: {}", .{_4}); - std.log.info("_1: {}", .{_5}); - - const fields = @typeInfo(shared.chunk.Chunk).@"struct".fields; - - inline for (fields) |field| { - const list = &@field(the_chunk, field.name); - - // const itemsType = @FieldType(field.type, "items"); - // const T = std.meta.Child(itemsType); - - for (list.items) |entity| { - - // const msg = shared.protocol.makeSpawnMessage(T, entity); - - // w.write(entity); - - std.log.info("{}", .{entity}); - - // try writer.writeAny(msg); - } + for (the_chunk.entities.items) |e| { + const msg = shared.protocol_v1.makeSpawnMessage(e); + try writer.serialize(allocator, msg); } - // const encoded = aw.written(); - - // std.log.info("{d} bytes: {s}", .{encoded.len, encoded}); - - // var buffer2: [4096]u8 = undefined; - // var fixed2 = std.io.Writer.fixed(&buffer2); - - // var inspector = bufzilla.Inspect(.{}).init(encoded, &fixed2, .{}); - // try inspector.inspect(); - - // std.log.info("{s}\n", .{fixed2.buffered()}); + const encoded = writer.written(); - // const packet = enet.enet_packet_create(encoded.ptr, encoded.len, enet.ENET_PACKET_FLAG_RELIABLE); + const packet = enet.enet_packet_create(encoded.ptr, encoded.len, enet.ENET_PACKET_FLAG_RELIABLE); - // enet.enet_host_broadcast(host, 0, packet); - _ = host; - // if (enet.enet_peer_send(peer, 0, packet) != 0) { - // std.log.err("Could not send packet to peer.", .{}); - // } + enet.enet_host_broadcast(host, 0, packet); } //fn handle_connection(connection: std.net.Server.Connection) !void {} diff --git a/src/shared/bits.zig b/src/shared/bits.zig index 992b7a2..f490bd0 100644 --- a/src/shared/bits.zig +++ b/src/shared/bits.zig @@ -1,5 +1,38 @@ const std = @import("std"); +// pub fn serialize(writer: *BitWriter, allocator: std.mem.Allocator, comptime T: type, value: T) !void { +// const info = @typeInfo(T); + +// switch (info) { +// .int, .bool => { +// try writer.write(allocator, T, value); +// }, + +// .@"struct" => |s| { +// if (!s.is_packed) +// @compileError("serialize only supports packed structs (got " ++ @typeName(T) ++ ")"); + +// inline for (s.fields) |field| { +// const field_value = @field(value, field.name); +// try serialize(writer, allocator, field_value); +// } +// }, + +// .@"union" => |u| { +// if (u.layout != .@"packed") +// @compileError("serialize only supports packed structs (got " ++ @typeName(T) ++ ")"); + +// inline for (u.fields) |field| { +// const field_value = @field(value, field.name); +// try serialize(writer, allocator, field_value); +// } +// }, + +// else => @compileError("Unsupported type in serialize: " ++ @typeName(T)), +// } +// } + + pub const BitWriter = struct { // We store a pointer to the ArrayList so we can grow it bytes: *std.ArrayList(u8), @@ -9,15 +42,54 @@ pub const BitWriter = struct { return .{ .bytes = bytes }; } - pub fn write(self: *BitWriter, allocator: std.mem.Allocator, value: anytype) !void { + pub fn serialize(self: *BitWriter, allocator: std.mem.Allocator, value: anytype) !void { const T = @TypeOf(value); const info = @typeInfo(T); - const bit_width = switch (info) { - .int => |int| int.bits, - .bool => 1, - else => @compileError("Unsupported type: " ++ @typeName(T)), - }; + switch (info) { + .@"struct" => |s| { + inline for (s.fields) |f| + try self.serialize(allocator, @field(value, f.name)); + }, + .@"union" => |u| { + const E = u.tag_type orelse @compileError(std.fmt.comptimePrint("Union {s} has to tag type.", .{@typeName(T)})); + try self.serialize(allocator, @as(E, value)); + switch (value) { + inline else => |f| try self.serialize(allocator, f), + } + }, + .@"enum" => try self.serialize(allocator, @intFromEnum(value)), + .pointer => |p| switch (p.size) { + .one => try self.serialize(allocator, value.*), + .slice => { + try self.serialize(allocator, value.len); + for (value) |e| + try self.serialize(allocator, e); + }, + .many, .c => @compileError(std.fmt.comptimePrint("Unsupported type: {}", .{T})), + }, + .int, .bool, .float, => try self.write(allocator, value), + .void => {}, + .array => |a| if (@sizeOf(a.child) == 1) { + try self.write(allocator, @ptrCast(&value)); + } else { + for (value) |e| + try self.serialize(allocator, e); + }, + .optional => if (value) |_t| { + try self.serialize(allocator, true); + try self.serialize(allocator, _t); + } else { + try self.serialize(allocator, false); + }, + .vector => |v| try self.serialize(allocator, @as([v.len]v.child, value)), + else => @compileError(std.fmt.comptimePrint("Unsupported type: {}", .{T})), + } + } + + fn write(self: *BitWriter, allocator: std.mem.Allocator, value: anytype) !void { + const T = @TypeOf(value); + const bit_width = @bitSizeOf(T); const UnsignedT = std.meta.Int(.unsigned, bit_width); const bits_to_write = @as(UnsignedT, @bitCast(value)); @@ -60,14 +132,64 @@ pub const BitReader = struct { return .{ .bytes = bytes }; } - pub fn read(self: *BitReader, comptime T: type) T { - const info = @typeInfo(T); - - const bit_width = switch (info) { - .int => |int| int.bits, - .bool => 1, - else => @compileError("Unsupported type: " ++ @typeName(T)), + pub fn deserialize(self: *BitReader, allocator: std.mem.Allocator, T: type) !T { + return switch (@typeInfo(T)) { + .@"struct" => |s| b: { + var data: T = undefined; + inline for (s.fields) |f| + @field(data, f.name) = try self.deserialize(allocator, f.type); + break :b data; + }, + .@"union" => |u| switch (try self.deserialize(allocator, u.tag_type.?)) { + inline else => |t| b: { + var data: T = @unionInit(T, @tagName(t), undefined); + const field = &@field(data, @tagName(t)); + field.* = try self.deserialize(allocator, @TypeOf(field.*)); + break :b data; + }, + }, + .@"enum" => |e| @enumFromInt(try self.deserialize(allocator, e.tag_type)), + .pointer => |p| switch (p.size) { + .one => b: { + const ptr = try allocator.create(p.child); + errdefer allocator.destroy(ptr); + ptr.* = try self.deserialize(allocator, p.child); + break :b ptr; + }, + .slice => b: { + const slice = try allocator.alloc(p.child, try self.deserialize(allocator, usize)); + errdefer allocator.free(slice); + for (slice) |*e| + e.* = try self.deserialize(allocator, p.child); + break :b slice; + }, + .many, .c => @compileError(std.fmt.comptimePrint("Unsupported type: {}", .{T})), + }, + .int, .bool, .float => self.read(T), + .void => {}, + .array => |a| b: { + var array: T = undefined; + if (@sizeOf(a.child) == 1) { + try self.read(&array); + } else { + for (&array) |*e| + e.* = try self.deserialize(allocator, a.child); + } + break :b array; + }, + .optional => |o| if (try self.deserialize(allocator, bool)) + try self.deserialize(o.child, allocator) + else + null, + .vector => |v| try self.deserialize(allocator, [v.len]v.child), + else => @compileError(std.fmt.comptimePrint("Unsupported type: {}", .{T})), }; + } + + pub fn read(self: *BitReader, comptime T: type) T { + // const info = @typeInfo(T); + + const bit_width = @bitSizeOf(T); const UnsignedT = std.meta.Int(.unsigned, bit_width); @@ -98,3 +220,67 @@ pub const BitReader = struct { return @bitCast(result); } }; + +test "Bit Stream" { + std.debug.print("Bit Stream\n", .{}); + const allocator = std.testing.allocator; + + var aw = try std.ArrayList(u8).initCapacity(allocator, 128); + defer aw.deinit(allocator); + + var writer = BitWriter.init(&aw); + + const _a : u3 = 0x5; + const _b : bool = true; + const _c : u3 = 0x1; + const _d : u4 = 0xE; + const _e : u32 = 0xFFFFFFFA; + const _f : i32 = -3; + const _g : f32 = 6.667; + + const ts = struct { + a: i32, + n: []const u8, + + pub fn format(self: @This(), w: *std.io.Writer) !void { + + try w.print(".{{ .a = {}, .n = \"{s}\" }}", .{ self.a, self.n }); + } + }; + + const _h : ts = .{ + .a = 44, + .n = "hello", + }; + + try writer.serialize(allocator, _a); + try writer.serialize(allocator, _b); + try writer.serialize(allocator, _c); + try writer.serialize(allocator, _d); + try writer.serialize(allocator, _e); + try writer.serialize(allocator, _f); + try writer.serialize(allocator, _g); + try writer.serialize(allocator, _h); + + var reader = BitReader.init(writer.written()); + + const _1 : u3 = try reader.deserialize(allocator, u3); + const _2 : bool = try reader.deserialize(allocator, bool); + const _3 : u3 = try reader.deserialize(allocator, u3); + const _4 : u4 = try reader.deserialize(allocator, u4); + const _5 : u32 = try reader.deserialize(allocator, u32); + const _6 : i32 = try reader.deserialize(allocator, i32); + const _7 = try reader.deserialize(allocator, f32); + const _8 = try reader.deserialize(allocator, ts); + defer allocator.free(_8.n); + + try std.testing.expectEqual(_a, _1); + try std.testing.expectEqual(_b, _2); + try std.testing.expectEqual(_c, _3); + try std.testing.expectEqual(_d, _4); + try std.testing.expectEqual(_e, _5); + try std.testing.expectEqual(_f, _6); + try std.testing.expectEqual(_g, _7); + + try std.testing.expectEqualDeep(_h, _8); +} diff --git a/src/shared/chunk.zig b/src/shared/chunk.zig index c1764f2..00362a6 100644 --- a/src/shared/chunk.zig +++ b/src/shared/chunk.zig @@ -26,63 +26,96 @@ const misc = @import("misc.zig"); // }; // } -pub const Chunk = _Chunk(); +// pub const Chunk = _Chunk(); -fn _Chunk() type { - const FieldCount = entity.EntityKinds.len; +// fn _Chunk() type { +// const FieldCount = entity.EntityKinds.len; - var fields: [FieldCount]std.builtin.Type.StructField = undefined; +// var fields: [FieldCount]std.builtin.Type.StructField = undefined; - inline for (entity.EntityKinds, 0..) |T, i| { - fields[i] = .{ - .name = misc.short_type_name(T), - .type = std.ArrayList(T), - .default_value_ptr = null, - .is_comptime = false, - .alignment = @alignOf(std.ArrayList(T)), +// inline for (entity.EntityKinds, 0..) |T, i| { +// fields[i] = .{ +// .name = misc.short_type_name(T), +// .type = std.ArrayList(T), +// .default_value_ptr = null, +// .is_comptime = false, +// .alignment = @alignOf(std.ArrayList(T)), +// }; +// } + +// return @Type(.{ +// .@"struct" = .{ +// .layout = .auto, +// .fields = &fields, +// .decls = &.{}, +// .is_tuple = false, +// }, +// }); +// } + +// pub fn initChunk(allocator: std.mem.Allocator) !Chunk { +// var chunk: Chunk = undefined; + +// inline for (@typeInfo(Chunk).@"struct".fields) |field| { +// const ListT = field.type; +// @field(chunk, field.name) = try ListT.initCapacity(allocator, 32); +// } + +// return chunk; +// } + +// pub fn deinitChunk(chunk: *Chunk, allocator: std.mem.Allocator) void { +// switch (@typeInfo(Chunk)) { +// .@"struct" => |s| { +// inline for (s.fields) |field| { +// @field(chunk, field.name).deinit(allocator); +// } +// }, +// else => unreachable, +// } +// } + +// pub fn updateChunk(chunk: *Chunk) void { +// inline for (@typeInfo(Chunk).@"struct".fields) |field| { +// const list = &@field(chunk, field.name); + +// for (list.items, 0..) |*item, i| { +// _ = i; +// if (@hasDecl(@TypeOf(item.*), "update")) +// item.update(); +// } +// } +// } + +pub const Chunk = struct { + entities : std.ArrayList(entity.Ref), + + pub fn init(allocator: std.mem.Allocator) !Chunk { + return .{ + .entities = try std.ArrayList(entity.Ref).initCapacity(allocator, 128), }; } - return @Type(.{ - .@"struct" = .{ - .layout = .auto, - .fields = &fields, - .decls = &.{}, - .is_tuple = false, - }, - }); -} - -pub fn initChunk(allocator: std.mem.Allocator) !Chunk { - var chunk: Chunk = undefined; - - inline for (@typeInfo(Chunk).@"struct".fields) |field| { - const ListT = field.type; - @field(chunk, field.name) = try ListT.initCapacity(allocator, 32); - } - - return chunk; -} - -pub fn deinitChunk(chunk: *Chunk, allocator: std.mem.Allocator) void { - switch (@typeInfo(Chunk)) { - .@"struct" => |s| { - inline for (s.fields) |field| { - @field(chunk, field.name).deinit(allocator); + pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + for (self.entities.items) |ref| { + switch (ref) { + inline else => |ptr| allocator.destroy(ptr), } - }, - else => unreachable, + } + + self.entities.deinit(allocator); } -} -pub fn updateChunk(chunk: *Chunk) void { - inline for (@typeInfo(Chunk).@"struct".fields) |field| { - const list = &@field(chunk, field.name); - - for (list.items, 0..) |*item, i| { - _ = i; - if (@hasDecl(@TypeOf(item.*), "update")) - item.update(); + pub fn update(self: *@This(), dt: f32) void { + for (self.entities.items) |e| { + switch (e) { + .Soldier => |s| { + s.update(dt); + }, + .Alien => |a| { + a.update(dt); + }, + } } } -} +}; diff --git a/src/shared/entity.zig b/src/shared/entity.zig index ae161e0..f1b7631 100644 --- a/src/shared/entity.zig +++ b/src/shared/entity.zig @@ -1,7 +1,6 @@ -const std = @import("std"); -const zm = @import("zmath"); -const protocol = @import("protocol.zig"); -const misc = @import("misc.zig"); +const std = @import("std"); +const zm = @import("zmath"); +const misc = @import("misc.zig"); pub const id = u64; pub const INVALID_ID: id = 0; @@ -11,39 +10,59 @@ pub const EntityKinds = .{ Alien, }; +const Entity = struct { + +}; + pub const Soldier = struct { id: id = INVALID_ID, - hp: i32, - pos: zm.Vec, - vel: zm.Vec, + hp: i32 = 0, + pos: zm.Vec = .{0, 0, 0, 0}, + vel: zm.Vec = .{0, 0, 0, 0}, - pub fn update(self: *Soldier) void { - self.pos += self.vel; + pub fn update(self: *@This(), dt: f32) void { + self.pos = self.pos + self.vel * zm.splat(zm.Vec, dt); } }; pub const Alien = struct { id: id = INVALID_ID, - pos: zm.Vec, - vel: zm.Vec, - hp: i32, + hp: i32 = 0, + pos: zm.Vec = .{0, 0, 0, 0}, + vel: zm.Vec = .{0, 0, 0, 0}, - pub fn update(self: *Alien) void { - self.pos = self.pos + self.vel; + pub fn update(self: *@This(), dt: f32) void { + self.pos = self.pos + self.vel * zm.splat(zm.Vec, dt); } }; -fn makeUnion() type { +fn _Ref() type { const FieldCount = EntityKinds.len; + var tag_fields: [FieldCount]std.builtin.Type.EnumField = undefined; + + inline for (EntityKinds, 0..) |T, i| { + tag_fields[i] = .{ + .name = misc.short_type_name(T), + .value = i, + }; + } + + const Tag = @Type(.{ + .@"enum" = .{ + .tag_type = u8, + .fields = &tag_fields, + .decls = &.{}, + .is_exhaustive = true, + }, + }); + var fields: [FieldCount]std.builtin.Type.UnionField = undefined; inline for (EntityKinds, 0..) |T, i| { fields[i] = .{ .name = misc.short_type_name(T), .type = *T, - .default_value_ptr = null, - .is_comptime = false, .alignment = @alignOf(*T), }; } @@ -53,9 +72,32 @@ fn makeUnion() type { .layout = .auto, .fields = &fields, .decls = &.{}, - .tag_type = u8, + .tag_type = Tag, }, }); } -const EntityRef = makeUnion(); +pub const Ref = _Ref(); + +pub fn setPos(self: Ref, pos: zm.Vec) void { + switch (self) { + .Soldier => |s| { s.pos = pos; }, + .Alien => |a| { a.pos = pos; }, + else => unreachable + } +} + +pub fn makeRef(comptime T: type, ptr: *T) Ref { + inline for (EntityKinds) |U| { + if (T == U) { + return @unionInit( + Ref, + misc.short_type_name(U), + ptr, + ); + } + } + + @compileError("Type not in EntityKinds: " ++ @typeName(T)); +} + diff --git a/src/shared/protocol.zig b/src/shared/protocol.zig deleted file mode 100644 index 6ee0d79..0000000 --- a/src/shared/protocol.zig +++ /dev/null @@ -1,129 +0,0 @@ -const std = @import("std"); -const zm = @import("zmath"); -// const bufzilla = @import("bufzilla"); - -const entity = @import("entity.zig"); -const bits = @import("bits.zig"); - -pub const SERVER_PORT: u16 = 1337; - -pub const Message = union(enum) { - spawn_entity: SpawnEntity, - // later: despawn_entity, update_entity, snapshot, etc. -}; - -pub const SpawnEntity = union(enum) { - soldier_v1: Soldier_v1, - alien_v1: Alien_v1, -}; - -pub fn makeSpawnMessage(comptime T: type, e: T) Message { - if (T == entity.Soldier) { - return .{ .spawn_entity = .{ .soldier_v1 = Soldier_v1.init(e) } }; - } else if (T == entity.Alien) { - return .{ .spawn_entity = .{ .alien_v1 = Alien_v1.init(e) } }; - } -} - -// pub fn write_message(writer: *std.Io.Writer, msg_type: MessageType, payload: []const u8) !void { -// try writer.writeByte(@intFromEnum(msg_type)); -// try writer.writeInt(u32, @intCast(payload.len), .little); -// try writer.writeAll(payload); -// } - -// pub fn read_message(reader: *std.Io.Reader, allocator: std.mem.Allocator) !struct { -// msg_type: MessageType, -// payload: []u8, -// } { -// const msg_type = try reader.readByte(); -// const size = try reader.readInt(u32, .little); - -// const payload = try allocator.alloc(u8, size); -// errdefer allocator.free(payload); - -// try reader.readNoEof(payload); - -// return .{ -// .msg_type = @enumFromInt(msg_type), -// .payload = payload, -// }; -// } - -pub const Soldier_v1 = struct { - id: entity.id = entity.INVALID_ID, - hp: i32, - pos: zm.Vec, - vel: zm.Vec, - - pub fn init(soldier: entity.Soldier) Soldier_v1 { - return .{ - .id = soldier.id, - .hp = soldier.hp, - .pos = soldier.pos, - .vel = soldier.vel, - }; - } - - pub fn decode() !Soldier_v1 { - const soldier_v1 : Soldier_v1 = .{ - .id = 0, - .hp = 0, - .pos = .{0, 0, 0, 0}, - .vel = .{0, 0, 0, 0}, - }; - - - - // switch (kv.value) { - // .i32 => |val| { - // if (std.mem.eql(u8, kv.key.bytes, "hp")) soldier_v1.hp = val; - // }, - // else => {} - // } - - return soldier_v1; - } -}; - -pub const Alien_v1 = struct { - id: entity.id = entity.INVALID_ID, - pos: zm.Vec, - hp: i32, - - pub fn init(alien: entity.Alien) Alien_v1 { - return .{ - .id = alien.id, - .pos = alien.pos, - .hp = alien.hp, - }; - } - - // pub fn encode(self: Alien_v1, w: *std.Io.Writer) !void { - // try w.writeInt(u64, self.id, .little); - // try writeVec4(w, self.pos); - // try writeVec4(w, self.vel); - // } - - pub fn decode(r: *std.Io.Reader) !Alien_v1 { - return .{ - .id = try r.readInt(u64, .little), - .pos = try readVec4(r), - .vel = try readVec4(r), - }; - } -}; - -fn writeVec4(w: *std.Io.Writer, v: zm.Vec) !void { - const a = zm.vecToArr4(v); - inline for (a) |f| { - try w.writeFloat(f32, f, .little); - } -} - -fn readVec4(r: *std.Io.Reader) !zm.Vec4 { - var a: [4]f32 = undefined; - inline for (&a) |*f| { - f.* = try r.readFloat(f32, .little); - } - return zm.loadArr4(a); -} diff --git a/src/shared/shared.zig b/src/shared/shared.zig index d74da14..54f12c7 100644 --- a/src/shared/shared.zig +++ b/src/shared/shared.zig @@ -1,5 +1,10 @@ -pub const entity = @import("entity.zig"); -pub const chunk = @import("chunk.zig"); -pub const misc = @import("misc.zig"); -pub const protocol = @import("protocol.zig"); -pub const bits = @import("bits.zig"); +pub const entity = @import("entity.zig"); +pub const chunk = @import("chunk.zig"); +pub const misc = @import("misc.zig"); +pub const protocol_v1 = @import("protocol_v1.zig"); +pub const bits = @import("bits.zig"); + +test { + const std = @import("std"); + std.testing.refAllDecls(@This()); +} diff --git a/vendor/freetype/build.zig.zon b/vendor/freetype/build.zig.zon index bd7cd72..6dee92a 100644 --- a/vendor/freetype/build.zig.zon +++ b/vendor/freetype/build.zig.zon @@ -1,5 +1,6 @@ .{ .name = .freetype, + .fingerprint = 0xac2059b69e282536, .version = "0.1.0", .minimum_zig_version = "0.15.2", .paths = .{