From 7d608435986846bfcec4292b0f9bf84351f9bbce Mon Sep 17 00:00:00 2001 From: Vicente Ferrari Smith Date: Sun, 21 Dec 2025 23:02:59 +0100 Subject: [PATCH] added client server shared --- .zed/debug.json | 27 +++++++++++ .zed/tasks.json | 103 ++++++++++++++++++++++++++++++++++++++++ src/client/main.zig | 77 ++++++++++++++++++++++++++++++ src/server/main.zig | 83 ++++++++++++++++++++++++++++++++ src/shared/chunk.zig | 99 ++++++++++++++++++++++++++++++++++++++ src/shared/entity.zig | 36 ++++++++++++++ src/shared/misc.zig | 7 +++ src/shared/protocol.zig | 15 ++++++ src/shared/shared.zig | 4 ++ zzz.rad_proj | 31 ++++++++++++ 10 files changed, 482 insertions(+) create mode 100644 .zed/debug.json create mode 100644 .zed/tasks.json create mode 100644 src/client/main.zig create mode 100644 src/server/main.zig create mode 100644 src/shared/chunk.zig create mode 100644 src/shared/entity.zig create mode 100644 src/shared/misc.zig create mode 100644 src/shared/protocol.zig create mode 100644 src/shared/shared.zig create mode 100644 zzz.rad_proj diff --git a/.zed/debug.json b/.zed/debug.json new file mode 100644 index 0000000..666160e --- /dev/null +++ b/.zed/debug.json @@ -0,0 +1,27 @@ +// Project-local debug tasks +// +// For more documentation on how to configure debug tasks, +// see: https://zed.dev/docs/debugger +[ + { + "adapter": "CodeLLDB", + "label": "zig build run", + "request": "launch", + "build": { + "label": "zig build", + "command": "zig", + "args": ["build"], + "env": {}, + "cwd": null, + "use_new_terminal": false, + "allow_concurrent_runs": false, + "reveal": "always", + "reveal_target": "dock", + "hide": "never", + //"tags": [], + "shell": "system", + "show_summary": false, + "show_command": false, + }, + }, +] diff --git a/.zed/tasks.json b/.zed/tasks.json new file mode 100644 index 0000000..b961529 --- /dev/null +++ b/.zed/tasks.json @@ -0,0 +1,103 @@ +// Project tasks configuration. See https://zed.dev/docs/tasks for documentation. +// +// Example: +[ + { + "label": "Example task", + "command": "for i in {1..5}; do echo \"Hello $i/5\"; sleep 1; done", + //"args": [], + // Env overrides for the command, will be appended to the terminal's environment from the settings. + "env": { "foo": "bar" }, + // Current working directory to spawn the command into, defaults to current project root. + //"cwd": "/path/to/working/directory", + // Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`. + "use_new_terminal": false, + // Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`. + "allow_concurrent_runs": false, + // What to do with the terminal pane and tab, after the command was started: + // * `always` — always show the task's pane, and focus the corresponding tab in it (default) + // * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it + // * `never` — do not alter focus, but still add/reuse the task's tab in its pane + "reveal": "always", + // Where to place the task's terminal item after starting the task: + // * `dock` — in the terminal dock, "regular" terminal items' place (default) + // * `center` — in the central pane group, "main" editor area + "reveal_target": "dock", + // What to do with the terminal pane and tab, after the command had finished: + // * `never` — Do nothing when the command finishes (default) + // * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it + // * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always` + "hide": "never", + // Which shell to use when running a task inside the terminal. + // May take 3 values: + // 1. (default) Use the system's default terminal configuration in /etc/passwd + // "shell": "system" + // 2. A program: + // "shell": { + // "program": "sh" + // } + // 3. A program with arguments: + // "shell": { + // "with_arguments": { + // "program": "/bin/bash", + // "args": ["--login"] + // } + // } + "shell": "system", + // Whether to show the task line in the output of the spawned task, defaults to `true`. + "show_summary": true, + // Whether to show the command line in the output of the spawned task, defaults to `true`. + "show_command": true, + // Represents the tags for inline runnable indicators, or spawning multiple tasks at once. + // "tags": [] + }, + { + "label": "zig build", + "command": "zig build", + //"args": [], + // Env overrides for the command, will be appended to the terminal's environment from the settings. + //"env": { "foo": "bar" }, + // Current working directory to spawn the command into, defaults to current project root. + //"cwd": "/path/to/working/directory", + // Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`. + "use_new_terminal": false, + // Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`. + "allow_concurrent_runs": false, + // What to do with the terminal pane and tab, after the command was started: + // * `always` — always show the task's pane, and focus the corresponding tab in it (default) + // * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it + // * `never` — do not alter focus, but still add/reuse the task's tab in its pane + "reveal": "always", + // Where to place the task's terminal item after starting the task: + // * `dock` — in the terminal dock, "regular" terminal items' place (default) + // * `center` — in the central pane group, "main" editor area + "reveal_target": "dock", + // What to do with the terminal pane and tab, after the command had finished: + // * `never` — Do nothing when the command finishes (default) + // * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it + // * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always` + "hide": "never", + // Which shell to use when running a task inside the terminal. + // May take 3 values: + // 1. (default) Use the system's default terminal configuration in /etc/passwd + // "shell": "system" + // 2. A program: + // "shell": { + // "program": "sh" + // } + // 3. A program with arguments: + // "shell": { + // "with_arguments": { + // "program": "/bin/bash", + // "args": ["--login"] + // } + // } + "shell": "system", + // Whether to show the task line in the output of the spawned task, defaults to `true`. + "show_summary": true, + // Whether to show the command line in the output of the spawned task, defaults to `true`. + "show_command": true, + // Represents the tags for inline runnable indicators, or spawning multiple tasks at once. + // "tags": [] + }, +] diff --git a/src/client/main.zig b/src/client/main.zig new file mode 100644 index 0000000..bacdae8 --- /dev/null +++ b/src/client/main.zig @@ -0,0 +1,77 @@ +const std = @import("std"); +const zm = @import("zmath"); +const shared = @import("shared"); + +var stdout: *std.io.Writer = undefined; + +pub fn main() !void { + std.log.info("Helllo Client!", .{}); + var dbg_allocator = std.heap.DebugAllocator(.{}).init; + defer _ = dbg_allocator.deinit(); + const allocator = dbg_allocator.allocator(); + + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + stdout = &stdout_writer.interface; + + std.log.info("{s}", .{@typeName(shared.chunk.Chunk)}); + + try stdout.flush(); + + const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol.SERVER_PORT); + + var connection = try std.net.tcpConnectToAddress(address); + + std.log.info("Connected to server", .{}); + + var the_chunk = try shared.chunk.initChunk(allocator); + defer shared.chunk.deinitChunk(&the_chunk, allocator); + + shared.chunk.spawn(&the_chunk, shared.entity.Player, allocator, .{ + .pos = zm.f32x4(1, 1, 0, 0), + .hp = 10, + }); + + shared.chunk.spawn(&the_chunk, shared.entity.Monster, allocator, .{ + .pos = zm.f32x4(1, 1, 0, 0), + .hp = 20, + }); + + shared.chunk.spawn(&the_chunk, shared.entity.Projectile, allocator, .{ + .pos = zm.f32x4(0, 0, 0, 0), + .vel = zm.f32x4(0.2, 0, 0, 0), + }); + + shared.chunk.updateChunk(&the_chunk); + + var send_buf: [1024]u8 = undefined; + var writer = connection.writer(&send_buf); + + try shared.protocol.sendHello(&writer.interface, .{ .msg = "Hello from client" }); + + var recv_buf: [1024]u8 = undefined; + var reader = connection.reader(&recv_buf); + const line = try shared.protocol.recvLine(reader.interface()); + std.log.info("{s}", .{line}); +} + +//fn handle_connection(connection: std.net.Server.Connection) !void {} + +// test "simple test" { +// const gpa = std.testing.allocator; +// var list: std.ArrayList(i32) = .empty; +// defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! +// try list.append(gpa, 42); +// try std.testing.expectEqual(@as(i32, 42), list.pop()); +// } + +// test "fuzz example" { +// const Context = struct { +// fn testOne(context: @This(), input: []const u8) anyerror!void { +// _ = context; +// // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! +// try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); +// } +// }; +// try std.testing.fuzz(Context{}, Context.testOne, .{}); +// } diff --git a/src/server/main.zig b/src/server/main.zig new file mode 100644 index 0000000..387f1f0 --- /dev/null +++ b/src/server/main.zig @@ -0,0 +1,83 @@ +const std = @import("std"); +const zm = @import("zmath"); +const shared = @import("shared"); + +var stdout: *std.io.Writer = undefined; + +pub fn main() !void { + std.log.info("Helllo Server!", .{}); + var dbg_allocator = std.heap.DebugAllocator(.{}).init; + defer _ = dbg_allocator.deinit(); + const allocator = dbg_allocator.allocator(); + + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + stdout = &stdout_writer.interface; + + std.log.info("{s}", .{@typeName(shared.chunk.Chunk)}); + + try stdout.flush(); + + const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol.SERVER_PORT); + var server = try address.listen(.{}); + + defer server.deinit(); + + var the_chunk = try shared.chunk.initChunk(allocator); + defer shared.chunk.deinitChunk(&the_chunk, allocator); + + shared.chunk.spawn(&the_chunk, shared.entity.Player, allocator, .{ + .pos = zm.f32x4(1, 1, 0, 0), + .hp = 10, + }); + + shared.chunk.spawn(&the_chunk, shared.entity.Monster, allocator, .{ + .pos = zm.f32x4(1, 1, 0, 0), + .hp = 20, + }); + + shared.chunk.spawn(&the_chunk, shared.entity.Projectile, allocator, .{ + .pos = zm.f32x4(0, 0, 0, 0), + .vel = zm.f32x4(0.2, 0, 0, 0), + }); + + shared.chunk.updateChunk(&the_chunk); + + while (true) { + const connection = try server.accept(); + defer connection.stream.close(); + + std.log.info("Client connected", .{}); + + var recv_buffer: [4096]u8 = undefined; + var send_buffer: [4096]u8 = undefined; + var reader = connection.stream.reader(&recv_buffer); + var writer = connection.stream.writer(&send_buffer); + + const line = try shared.protocol.recvLine(reader.interface()); + std.log.info("Received: {s}", .{line}); + + try shared.protocol.sendHello(&writer.interface, .{ .msg = "Hello from server!" }); + } +} + +//fn handle_connection(connection: std.net.Server.Connection) !void {} + +// test "simple test" { +// const gpa = std.testing.allocator; +// var list: std.ArrayList(i32) = .empty; +// defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! +// try list.append(gpa, 42); +// try std.testing.expectEqual(@as(i32, 42), list.pop()); +// } + +// test "fuzz example" { +// const Context = struct { +// fn testOne(context: @This(), input: []const u8) anyerror!void { +// _ = context; +// // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! +// try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); +// } +// }; +// try std.testing.fuzz(Context{}, Context.testOne, .{}); +// } diff --git a/src/shared/chunk.zig b/src/shared/chunk.zig new file mode 100644 index 0000000..c7f98dc --- /dev/null +++ b/src/shared/chunk.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const entity = @import("entity.zig"); +const misc = @import("misc.zig"); + +fn Storage(comptime T: type) type { + return struct { + items: std.ArrayList(T), + + pub fn init(allocator: std.mem.Allocator) !@This() { + return .{ + .items = try std.ArrayList(T).initCapacity(allocator, 32), + }; + } + + pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + self.items.deinit(allocator); + } + + pub fn update(self: *@This()) void { + for (self.items.items, 0..) |*item, i| { + _ = i; + if (@hasDecl(T, "update")) + item.update(); + } + } + }; +} + +pub fn initChunk(allocator: std.mem.Allocator) !Chunk { + var chunk: Chunk = undefined; + + switch (@typeInfo(Chunk)) { + .@"struct" => |s| { + inline for (s.fields) |field| { + const StorageT = field.type; + @field(chunk, field.name) = try StorageT.init(allocator); + } + }, + else => unreachable, + } + + 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, + } +} + +fn _Chunk(comptime Types: anytype) type { + const FieldCount = Types.len; + + var fields: [FieldCount]std.builtin.Type.StructField = undefined; + + inline for (Types, 0..) |T, i| { + fields[i] = .{ + .name = misc.short_type_name(T), + .type = Storage(T), + .default_value_ptr = null, + .is_comptime = false, + .alignment = @alignOf(Storage(T)), + }; + } + + return @Type(.{ + .@"struct" = .{ + .layout = .auto, + .fields = &fields, + .decls = &.{}, + .is_tuple = false, + }, + }); +} + +pub const Chunk = _Chunk(entity.EntityKinds); + +pub fn updateChunk(chunk: *Chunk) void { + const info = @typeInfo(Chunk); + + switch (info) { + .@"struct" => |s| { + inline for (s.fields) |field| { + @field(chunk, field.name).update(); + } + }, + else => unreachable, + } +} + +pub fn spawn(chunk: anytype, comptime T: type, allocator: std.mem.Allocator, value: T) void { + std.log.info("hello!? {s}", .{@typeName(T)}); + @field(chunk, misc.short_type_name(T)).items.append(allocator, value) catch unreachable; +} diff --git a/src/shared/entity.zig b/src/shared/entity.zig new file mode 100644 index 0000000..6dced51 --- /dev/null +++ b/src/shared/entity.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const zm = @import("zmath"); + +pub const EntityKinds = .{ + Player, + Monster, + Projectile, +}; + +pub const Player = struct { + pos: zm.Vec, + hp: i32, + + pub fn update(self: *Player) void { + std.log.info("pos=({})", .{self.pos}); + } +}; + +pub const Monster = struct { + pos: zm.Vec, + hp: i32, + + pub fn update(self: *Monster) void { + std.log.info("pos=({})", .{self.pos}); + } +}; + +pub const Projectile = struct { + pos: zm.Vec, + vel: zm.Vec, + + pub fn update(self: *Projectile) void { + self.pos = self.pos + self.vel; + std.log.info("pos=({})", .{self.pos}); + } +}; diff --git a/src/shared/misc.zig b/src/shared/misc.zig new file mode 100644 index 0000000..8efabc5 --- /dev/null +++ b/src/shared/misc.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn short_type_name(comptime T: type) [:0]const u8 { + const full = @typeName(T); + const base = full[std.mem.lastIndexOf(u8, full, ".").? + 1 ..]; + return base ++ "\x00"; +} diff --git a/src/shared/protocol.zig b/src/shared/protocol.zig new file mode 100644 index 0000000..8f1304e --- /dev/null +++ b/src/shared/protocol.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +pub const SERVER_PORT: u16 = 1337; + +pub const Hello = struct { + msg: []const u8, +}; + +pub fn sendHello(writer: *std.io.Writer, hello: Hello) !void { + try writer.print("{s}\n", .{hello.msg}); +} + +pub fn recvLine(reader: *std.io.Reader) ![]u8 { + return try reader.takeDelimiterExclusive('\n'); +} diff --git a/src/shared/shared.zig b/src/shared/shared.zig new file mode 100644 index 0000000..9951dfb --- /dev/null +++ b/src/shared/shared.zig @@ -0,0 +1,4 @@ +pub const entity = @import("entity.zig"); +pub const chunk = @import("chunk.zig"); +pub const misc = @import("misc.zig"); +pub const protocol = @import("protocol.zig"); diff --git a/zzz.rad_proj b/zzz.rad_proj new file mode 100644 index 0000000..d802c2e --- /dev/null +++ b/zzz.rad_proj @@ -0,0 +1,31 @@ +// raddbg 0.9.24 project file + +recent_file: path: "../../AppData/Local/Microsoft/WinGet/Packages/zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe/zig-x86_64-windows-0.15.2/lib/std/start.zig" +recent_file: path: "src/client/main.zig" +target: +{ + executable: "zig-out/bin/server.exe" + working_directory: "zig-out/bin/" + enabled: 1 +} +target: +{ + executable: "zig-out/bin/client.exe" + working_directory: "zig-out/bin/" + enabled: 1 +} +debug_info: +{ + path: "C:/Users/vfs/Dev/zzz/zig-out/bin/server.pdb" + timestamp: 66201747551645 +} +debug_info: +{ + path: "C:/Users/vfs/Dev/zzz/zig-out/bin/client.pdb" + timestamp: 66201747551558 +} +breakpoint: +{ + source_location: "src/client/main.zig:8:1" + hit_count: 1 +}