Compare commits

..
16 Commits
Author SHA1 Message Date
vfs 077ceb810e i don't know 2026-02-19 22:12:59 +01:00
vfs 35bbc5cbfd v 2026-02-08 15:22:38 +01:00
vfs 5a79ae71c0 . 2026-02-08 15:22:31 +01:00
vfs 29c136d34c ... 2026-02-05 12:32:12 +01:00
vfs 9a5c15e47e bit streaming 2026-02-05 12:32:01 +01:00
vfs 5864401129 . 2026-02-04 15:36:53 +01:00
vfs 5eba80a6c6 not even compiling :( gotta go to bed 2026-02-03 23:20:48 +01:00
vfs 395320a794 . 2026-02-03 19:27:23 +01:00
vfs bab331083e how to use bufzilla? 2026-02-03 17:49:10 +01:00
vfs fb238a1966 . 2026-02-03 13:22:20 +01:00
vfs c6fdd68afe . 2026-02-03 10:19:36 +01:00
vfs ee790cf700 working on protocol architecture 2026-02-02 23:42:54 +01:00
vfs 510c19fbd5 enet 2026-02-02 11:12:50 +01:00
vfs b91bce8840 enough fonting, more servering? 2026-02-02 01:25:01 +01:00
vfs e6d8607d3f . 2026-02-02 00:43:52 +01:00
vfs 719973ad19 actually fix the tracy version to a commit so we don't have to regenerate a new hash every time someone makes a commit on this very popular repository! 2026-02-02 00:43:45 +01:00
22 changed files with 1081 additions and 611 deletions

No files matched your search

+13 -2
View File
@@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) Launch",
"name": "Client",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/client",
@@ -13,7 +13,18 @@
"cwd": "${workspaceFolder}",
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "build",
"sourceLanguages": ["zig"]
"sourceLanguages": ["zig"],
},
{
"name": "Server",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/server",
"args": [],
"cwd": "${workspaceFolder}",
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "build",
"sourceLanguages": ["zig"],
}
]
}
+13 -1
View File
@@ -6,7 +6,19 @@
{
"label": "build",
"type": "shell",
"command": "zig build",
"command": "zig",
"args": ["build"],
"options": {
"cwd": "${workspaceRoot}"
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true,
},
"problemMatcher": [],
"group": {
"kind": "build",
+39 -28
View File
@@ -10,7 +10,7 @@ pub fn build(b: *std.Build) void {
.{},
});
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .Debug });
const assets = b.addInstallDirectory(.{
.source_dir = b.path("assets"),
@@ -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"));
}
@@ -131,21 +137,23 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
// shared.linkLibrary(enet.artifact("enet"));
client.root_module.linkLibrary(enet.artifact("enet"));
server.root_module.linkLibrary(enet.artifact("enet"));
}
// Bufzilla
{
const bufzilla = b.dependency(
"bufzilla",
.{
.target = target,
.optimize = optimize,
});
client.root_module.addImport("bufzilla", bufzilla.module("bufzilla"));
server.root_module.addImport("bufzilla", bufzilla.module("bufzilla"));
}
// // Bufzilla
// {
// const bufzilla = b.dependency(
// "bufzilla",
// .{
// .target = target,
// .optimize = optimize,
// });
// shared.addImport("bufzilla", bufzilla.module("bufzilla"));
// client.root_module.addImport("bufzilla", bufzilla.module("bufzilla"));
// server.root_module.addImport("bufzilla", bufzilla.module("bufzilla"));
// }
// Raylib
{
@@ -168,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,
@@ -205,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.
+2 -2
View File
@@ -44,8 +44,8 @@
.path = "vendor/enet",
},
.bufzilla = .{
.url = "https://github.com/theseyan/bufzilla/archive/refs/tags/v0.3.2.tar.gz",
.hash = "bufzilla-0.3.0-gU6dXi67AQAg3WIFrNQ0iafrvexj3iBwVcczrVzrN3Ir",
.url = "https://github.com/theseyan/bufzilla/archive/refs/tags/v0.3.3.tar.gz",
.hash = "bufzilla-0.3.0-gU6dXie7AQDTXB7ptRjLa1plj3txaUVKFvT39e3v-pop",
},
.tracy = .{
.path = "vendor/tracy",
+73
View File
@@ -0,0 +1,73 @@
const std = @import("std");
const shared = @import("shared");
const enet = @import("c.zig").enet;
pub fn spawn(self: *shared.chunk.Chunk, allocator: std.mem.Allocator, comptime T: type, value: T) !shared.entity.id {
std.debug.assert(value.id != shared.entity.INVALID_ID);
const ptr = try allocator.create(T);
errdefer allocator.destroy(ptr);
ptr.* = value;
const ref = shared.entity.makeRef(T, ptr);
try self.entities.append(allocator, ref);
return ptr.id;
}
pub fn find(self: *shared.chunk.Chunk, id: shared.entity.id) ?shared.entity.Ref {
for (self.entities.items) |ref| {
switch (ref) {
inline else => |ptr| {
if (ptr.id == id) {
return ref;
}
}
}
}
return null;
}
pub fn findT(self: *shared.chunk.Chunk, comptime T: type, id: shared.entity.id) ?*T {
for (self.entities.items) |ref| {
switch (ref) {
inline else => |ptr| {
if (ptr.id == id and @TypeOf(ptr) == *T) {
return ptr;
}
}
}
}
return null;
}
pub fn despawn(self: *shared.chunk.Chunk, allocator: std.mem.Allocator, id: shared.entity.id) bool {
var i: usize = 0;
while (i < self.entities.items.len) {
const ref = self.entities.items[i];
var found = false;
switch (ref) {
inline else => |ptr| {
if (ptr.id == id) {
allocator.destroy(ptr);
found = true;
}
},
}
if (found) {
_ = self.entities.swapRemove(i);
return true;
}
i += 1;
}
return false;
}
+2 -1
View File
@@ -1,4 +1,5 @@
const std = @import("std");
const std = @import("std");
const shared = @import("shared");
const ConnectState = union(enum) {
disconnected,
+23 -15
View File
@@ -2,21 +2,29 @@ const rl = @import("raylib");
const shared = @import("shared");
const zm = @import("zmath");
pub const Elf = struct {
elf: shared.entity.Elf,
// pub const Elf = struct {
// elf: shared.entity.Elf,
boy: rl.Texture,
// boy: rl.Texture,
pub fn init() Elf {
return .{
.elf = .{ .hp = 10, .pos = zm.f32x4(0, 0, 0, 0) },
.boy = rl.loadTexture("assets/tile_0000.png") catch unreachable,
};
}
// pub fn init() Elf {
// return .{
// .elf = .{ .hp = 10, .pos = zm.f32x4(0, 0, 0, 0) },
// .boy = rl.loadTexture("assets/tile_0000.png") catch unreachable,
// };
// }
pub fn draw(self: *Elf) void {
const src = rl.Rectangle.init(0, 0, @floatFromInt(self.boy.width), @floatFromInt(self.boy.height));
const dst = rl.Rectangle.init(100 + 100 * zm.cos(@as(f32, @floatCast(rl.getTime()))), 100 + 100 * zm.sin(@as(f32, @floatCast(rl.getTime()))), 2.0 * @as(f32, @floatFromInt(self.boy.width)), 2.0 * @as(f32, @floatFromInt(self.boy.height)));
self.boy.drawPro(src, dst, .{ .x = 0, .y = 0 }, 0, .white);
}
};
// pub fn draw(self: *Elf) void {
// const src = rl.Rectangle.init(0, 0, @floatFromInt(self.boy.width), @floatFromInt(self.boy.height));
// const dst = rl.Rectangle.init(100 + 100 * zm.cos(@as(f32, @floatCast(rl.getTime()))), 100 + 100 * zm.sin(@as(f32, @floatCast(rl.getTime()))), 2.0 * @as(f32, @floatFromInt(self.boy.width)), 2.0 * @as(f32, @floatFromInt(self.boy.height)));
// self.boy.drawPro(src, dst, .{ .x = 0, .y = 0 }, 0, .white);
// }
// };
pub fn draw_soldier(self: *shared.entity.Soldier) void {
}
pub fn draw_alien(self: *shared.entity.Alien) void {
}
+126 -219
View File
@@ -1,6 +1,6 @@
const std = @import("std");
const zm = @import("zmath");
const bufzilla = @import("bufzilla");
// const bufzilla = @import("bufzilla");
const ft = @import("freetype");
const tracy = @import("tracy");
const kb = @import("kb");
@@ -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,7 +30,7 @@ var running: bool = true;
var dbg_allocator = std.heap.DebugAllocator(.{}){};
// var connection: ?std.net.Stream = undefined;
var the_chunk : shared.chunk.Chunk = undefined;
pub fn main() !void {
const tracy_zone = tracy.ZoneNC(@src(), "main", 0x00_ff_00_00);
@@ -40,8 +41,6 @@ pub fn main() !void {
const deinit_status = dbg_allocator.deinit();
if (deinit_status == .leak) {
@panic("LEAKED!");
} else {
// @panic("SUCCESS!");
}
}
@@ -56,7 +55,6 @@ pub fn main() !void {
defer enet.enet_deinitialize();
// rl.SetConfigFlags(.{ .window_highdpi = true, .vsync_hint = false });
rl.SetConfigFlags(rl.FLAG_WINDOW_HIGHDPI);
rl.InitWindow(screen_width, screen_height, "zzz");
defer rl.CloseWindow();
@@ -64,13 +62,10 @@ pub fn main() !void {
font.ft_lib = try ft.Library.init();
const sizes = [_]i32{
60,
30,
};
var vollkorn: [sizes.len]font.Font = undefined;
// var inconsolata: [sizes.len]font.Font = undefined;
// var arabic: [sizes.len]font.Font = undefined;
// // var japanese: [sizes.len]font.Font = undefined;
for (sizes, 0..) |pt, i| {
vollkorn[i] = try font.Font.init(
@@ -78,88 +73,27 @@ pub fn main() !void {
pt,
allocator
);
// inconsolata[i] = try font.Font.init(
// "assets/fonts/Inconsolata/static/Inconsolata-Regular.ttf",
// @intCast(pt),
// allocator
// );
// arabic[i] = try font.Font.init(
// "assets/fonts/Amiri/Amiri-Regular.ttf",
// @intCast(pt),
// allocator
// );
// japanese[i] = try font.Font.init(
// "assets/fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf",
// @intCast(pt),
// allocator
// );
}
defer {
for (&vollkorn) |*f| f.deinit(allocator);
// for (&inconsolata) |*f| f.deinit(allocator);
// for (&arabic) |*f| f.deinit(allocator);
// for (&japanese) |*f| f.deinit(allocator);
}
font.shader = rl.LoadShader(null, "assets/text.frag");
// const test_shader1 = try c.LoadShader(null, "assets/test_1.frag");
// const test_shader2 = try c.LoadShader(null, "assets/test_2.frag");
// const test_shader1 = try rl.LoadShader(null, "assets/test_1.frag");
// const test_shader2 = try rl.LoadShader(null, "assets/test_2.frag");
// font.shader = try rl.loadShader(null, "assets/text.frag");
// const test_shader1 = try rl.loadShader(null, "assets/test_1.frag");
// const test_shader2 = try rl.loadShader(null, "assets/test_2.frag");
const host = enet.enet_host_create(null, 32, 2, 0, 0);
defer enet.enet_host_destroy(host);
// const img = rl.genImageColor(32, 32, .blank);
// const tx = try rl.loadTextureFromImage(img);
// rl.unloadImage(img);
var address = enet.ENetAddress{ .port = shared.protocol_v1.SERVER_PORT };
_ = enet.enet_address_set_host(&address, "localhost");
// const host = try znet.Host.init(.{
// .addr = null,
// .peer_limit = 1,
// .channel_limit = .max,
// .incoming_bandwidth = .unlimited,
// .outgoing_bandwidth = .unlimited,
// });
// defer host.deinit();
const peer = enet.enet_host_connect(host, &address, 2, 0);
defer enet.enet_peer_reset(peer);
// const peer = try host.connect(.{
// .addr = try .init(.{
// .ip = .{ .ipv4 = "127.0.0.1" },
// .port = .{ .uint = 5000 },
// }),
// .channel_limit = .max,
// .data = 0,
// });
// connect() catch |err| switch (err) {
// error.ConnectionRefused => {
// std.log.err("server refused connection", .{});
// },
// else => {
// std.log.err("unexpected connect error: {}", .{err});
// return err;
// },
// };
// try stdout.flush();
// var the_chunk = try shared.chunk.initChunk(allocator);
// defer shared.chunk.deinitChunk(&the_chunk, allocator);
// shared.chunk.updateChunk(&the_chunk);
//var elf = entity.Elf.init();
// var send_buf: [1024]u8 = undefined;
// var writer = if (connection) |*conn| conn.writer(&send_buf) else return;
// try shared.protocol.sendHello(&writer.interface, .{ .msg = "Hello from client" });
// var recv_buf: [1024]u8 = undefined;
// var reader = if (connection) |*conn| conn.reader(&recv_buf) else return;
// const line = try reader.interface().takeDelimiterExclusive('\n');
// std.log.info("{s}", .{line});
the_chunk = try shared.chunk.Chunk.init(allocator);
defer the_chunk.deinit(allocator);
// const camera = rl.Camera{
// .fovy = 45,
@@ -182,14 +116,12 @@ pub fn main() !void {
// rl.playMusicStream(music);
// std.log.info("is music playing? {}", .{rl.isMusicStreamPlaying(music)});
// var old_time : f32 = @floatCast(rl.getTime());
var old_time : f32 = @floatCast(rl.GetTime());
while (!rl.WindowShouldClose()) {
const tracy_main_zone = tracy.ZoneNC(@src(), "loop", 0x00_ff_00_00);
const tracy_main_zone = tracy.ZoneNC(@src(), "loop", 0x00_aa_33_10);
defer tracy_main_zone.End();
// const new_time : f32 = @floatCast(rl.getTime());
const new_time : f32 = @floatCast(rl.GetTime());
var frame_time = new_time - old_time;
old_time = new_time;
@@ -200,29 +132,29 @@ pub fn main() !void {
t += frame_time;
accumulator += frame_time * k;
// std.log.info("t: {}", .{t});
// while (try host.service(0)) |event| switch (event) {
// .connect => |data| {
// _ = data;
// // std.log.info("{}", .{data.peer});
// },
// .disconnect => |data| {
// _ = data;
// // std.log.info("{}", .{data.peer});
// },
// .receive => |data| {
// defer data.packet.deinit();
var event = enet.ENetEvent{};
while (enet.enet_host_service(host, &event, 0) > 0) {
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
});
// const slice = data.packet.dataSlice();
event.peer.*.data = @constCast(@ptrCast("Client information"));
},
enet.ENET_EVENT_TYPE_RECEIVE => {
try on_packet(allocator, event.packet, event.peer, event.channelID);
// var reader = bufzilla.Reader(.{}).init(slice);
// const id = try reader.readPath("id");
// std.log.info("{any}", .{id});
// },
// };
enet.enet_packet_destroy(event.packet);
},
enet.ENET_EVENT_TYPE_DISCONNECT => {
std.log.info("disconnect", .{});
},
else => {}
}
}
rl.PollInputEvents();
@@ -235,7 +167,7 @@ pub fn main() !void {
// rl.updateMusicStream(music);
while (accumulator > dt * k) {
// update(dt * cast(float) k);
the_chunk.update(dt * k);
accumulator -= dt * k;
gt += dt * k;
}
@@ -247,6 +179,18 @@ pub fn main() !void {
// const lorem = @embedFile("embeds/lorem.txt");
for (the_chunk.entities.items) |e| {
switch (e) {
.Soldier => |soldier| {
entity.render_soldier(soldier);
},
.Alien => |alien| {
entity.render_alien(alien);
}
}
}
vollkorn[0].render_text(
"The night is long and cold outside.",
rl.Vector2{ .x = 0, .y = 0 },
@@ -256,118 +200,6 @@ pub fn main() !void {
false,
true
);
// arabic[0].render_text(
// "الليل طويل وبارد في الخارج",
// rl.Vector2{ .x = 0, .y = 100},
// true,
// .white,
// .blank,
// false,
// true
// );
// var y : f32 = 0;
// var it = std.mem.splitScalar(u8, lorem, '\n');
// while (it.next()) |line| {
// vollkorn[0].render_text(
// line,
// rl.Vector2{ .x = 0, .y = y},
// true,
// .white,
// .blank,
// false,
// true
// );
// const font_linegap : f32 = @floatFromInt(vollkorn[0].face.size().metrics().height >> 6);
// const dpi_font_linegap = font_linegap / rl.getWindowScaleDPI().y;
// y += dpi_font_linegap;
// }
// for (&vollkorn) |*f| {
// f.render_text(
// "Whereas, disregard and contempt for human rights have resulted!",
// rl.Vector2{ .x = 0, .y = y},
// true,
// .white,
// .blank,
// false,
// true
// );
// // const font_ascent : f32 = @floatFromInt(f.face.size().metrics().ascender >> 6);
// // const dpi_font_ascent = font_ascent / rl.getWindowScaleDPI().y;
// // const font_descent : f32 = @floatFromInt(f.face.size().metrics().descender >> 6);
// // const dpi_font_descent = font_descent / rl.getWindowScaleDPI().y;
// const font_linegap : f32 = @floatFromInt(f.face.size().metrics().height >> 6);
// const dpi_font_linegap = font_linegap / rl.getWindowScaleDPI().y;
// y += dpi_font_linegap;
// }
// for (&inconsolata) |*f| {
// f.render_text(
// "Whereas, disregard and contempt for human rights have resulted!",
// rl.Vector2{ .x = 0, .y = y},
// true,
// .white,
// .blank,
// false,
// true
// );
// // const font_ascent : f32 = @floatFromInt(f.face.size().metrics().ascender >> 6);
// // const dpi_font_ascent = font_ascent / rl.getWindowScaleDPI().y;
// // const font_descent : f32 = @floatFromInt(f.face.size().metrics().descender >> 6);
// // const dpi_font_descent = font_descent / rl.getWindowScaleDPI().y;
// const font_linegap : f32 = @floatFromInt(f.face.size().metrics().height >> 6);
// const dpi_font_linegap = font_linegap / rl.getWindowScaleDPI().y;
// y += dpi_font_linegap;
// }
// for (&japanese) |*f| {
// f.render_text(
// "外は夜が長くて寒い",
// c.Vector2{ .x = 0, .y = y},
// true,
// c.WHITE,
// c.BLANK,
// false,
// true
// );
// const font_ascent : f32 = @floatFromInt(f.face.size().metrics().ascender >> 6);
// const dpi_font_ascent = font_ascent / c.GetWindowScaleDPI().y;
// y += dpi_font_ascent;
// }
// for (&arabic) |*f| {
// const font_ascent : f32 = @floatFromInt(f.face.size().metrics().ascender >> 6);
// const dpi_font_ascent = font_ascent / rl.getWindowScaleDPI().y;
// y += dpi_font_ascent;
// }
// f.render_text(
// "Whereas, disregard and contempt for human rights have resulted!",
// rl.Vector2.init(0, 0),
// true,
// rl.Color.white,
// rl.Color.blank,
// false,
// true
// );
// rl.beginShaderMode(test_shader1);
// rl.gl.rlBegin(rl.gl.rl_quads);
@@ -494,7 +326,7 @@ pub fn main() !void {
rl.EndDrawing();
drawing_zone.End();
const swap_zone = tracy.ZoneNC(@src(), "swapScreenBuffer", 0x00_ff_00_00);
const swap_zone = tracy.ZoneNC(@src(), "SwapScreenBuffer", 0x00_ff_00_00);
rl.SwapScreenBuffer();
swap_zone.End();
@@ -504,8 +336,83 @@ pub fn main() !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,
// @as([*:0]const u8, @ptrCast(peer.*.data.?)),
// channelID});
_ = peer;
_ = channelID;
const bytes: []const u8 = packet.*.data[0 .. packet.*.dataLength];
var reader = shared.bits.BitReader.init(bytes);
const msg : shared.protocol_v1.Message = try reader.deserialize(allocator, shared.protocol_v1.Message);
try on_message(allocator, msg);
}
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); },
}
}
fn on_spawn_entity(allocator: std.mem.Allocator, p: shared.protocol_v1.EntityPayload) !void {
// _ = allocator; _ = se;
switch (p) {
.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);
},
}
}
fn on_update_entity(p: shared.protocol_v1.EntityPayload) void {
switch (p) {
.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, p: shared.protocol_v1.EntityPayload) void {
switch (p) {
.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);
+2 -2
View File
@@ -1,3 +1,3 @@
pub const c = @cImport({
// @cInclude("enet/enet.h");
pub const enet = @cImport({
@cInclude("enet/enet.h");
});
+37 -23
View File
@@ -1,42 +1,56 @@
const std = @import("std");
const bufzilla = @import("bufzilla");
// const bufzilla = @import("bufzilla");
const shared = @import("shared");
const server = @import("server.zig");
const enet = @import("c.zig").enet;
pub fn spawn(chunk: *shared.chunk.Chunk, comptime T: type, allocator: std.mem.Allocator, 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);
inline for (@typeInfo(shared.chunk.Chunk).@"struct".fields) |field| {
if (field.type == shared.chunk.Storage(T)) {
try @field(chunk, field.name).items.append(allocator, entity);
break;
}
}
ptr.* = value;
ptr.id = id;
const ref = shared.entity.makeRef(T, ptr);
try self.entities.append(allocator, ref);
_ = host;
// 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);
const msg = shared.protocol.makeSpawnMessage(T, entity);
// const data = fixed.buffered();
// const packet = enet.enet_packet_create(data.ptr, data.len + 1, enet.ENET_PACKET_FLAG_RELIABLE);
var writer = bufzilla.Writer.init(&fixed);
try writer.writeAny(msg);
// enet.enet_host_broadcast(host, 0, packet);
// const encoded = fixed.buffered();
return ptr.id;
}
// const packet = try znet.Packet.init(encoded, 0, .reliable);
// var iterator = server.host.iterPeers();
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);
// while (iterator.next()) |peer| {
// if (peer.state() == .connected) {
// try peer.send(packet);
// }
// }
var writer = shared.bits.BitWriter.init(&aw);
for (chunk.entities.items) |e| {
const msg = shared.protocol_v1.makeMessage(.update_entity, 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);
}
+118 -61
View File
@@ -1,83 +1,101 @@
const std = @import("std");
const zm = @import("zmath");
const std = @import("std");
const zm = @import("zmath");
const enet = @import("c.zig").enet;
// const bufzilla = @import("bufzilla");
const shared = @import("shared");
const chunk = @import("chunk.zig");
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 stdout: *std.io.Writer = undefined;
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 {
std.log.info("Hello Server!", .{});
var dbg_allocator = std.heap.DebugAllocator(.{}).init;
defer _ = dbg_allocator.deinit();
const allocator = dbg_allocator.allocator();
defer {
const deinit_status = dbg_allocator.deinit();
if (deinit_status == .leak) {
@panic("LEAKED!");
}
}
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
stdout = &stdout_writer.interface;
var stdout = std.fs.File.stdout();
_ = try stdout.write("Hello, Server!\n");
try stdout.flush();
if (enet.enet_initialize() != 0) {
log.info("Failed to load ENet", .{});
return;
}
defer enet.enet_deinitialize();
// try znet.init();
// defer znet.deinit();
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);
// server.host = try znet.Host.init(.{
// .addr = try .init(.{
// .ip = .any,
// .port = .{ .uint = 5000 },
// }),
// .peer_limit = 32,
// .channel_limit = .max,
// .incoming_bandwidth = .unlimited,
// .outgoing_bandwidth = .unlimited,
// });
var the_chunk = try shared.chunk.Chunk.init(allocator);
defer the_chunk.deinit(allocator);
// const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol.SERVER_PORT);
// var tcp_server = try address.listen(.{});
_ = try chunk.spawn(
&the_chunk,
allocator,
host,
shared.entity.Soldier,
.{
.hp = 10,
.pos = zm.f32x4(5.0, 0, 0, 0),
.vel = zm.f32x4(0, 0, 0, 0),
}
);
// defer tcp_server.deinit();
var last_time: i128 = std.time.nanoTimestamp();
var next_frame_deadline: i128 = last_time + frame_dt_ns;
var the_chunk = try shared.chunk.initChunk(allocator);
defer shared.chunk.deinitChunk(&the_chunk, allocator);
var old_time = std.time.nanoTimestamp();
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;
const delta_s: f32 = @floatCast(@as(f64, @floatFromInt(delta_ns)) / 1e9);
// std.log.info("t: {}", .{t});
t += delta_s;
accumulator += delta_s * k;
log.info("Starting a the frame {} at time {}", .{frame, t});
// while (try server.host.service(0)) |event| switch (event) {
// .connect => |data| {
// _ = data;
// // std.log.info("{}", .{data.peer});
// },
// .disconnect => |data| {
// _ = data;
// // std.log.info("{}", .{data.peer});
// },
// .receive => |data| {
// std.log.info("{s}", .{data.packet.dataSlice()});
// defer data.packet.deinit();
// },
// };
var event = enet.ENetEvent{};
while (enet.enet_host_service(host, &event, 0) > 0) {
switch (event.type) {
enet.ENET_EVENT_TYPE_CONNECT => {
try on_connect(allocator, host, event.peer, &the_chunk);
},
enet.ENET_EVENT_TYPE_RECEIVE => {
log.info("receive", .{});
},
enet.ENET_EVENT_TYPE_DISCONNECT => {
log.info("disconnect", .{});
},
else => {},
}
}
// const connection = try tcp_server.accept();
// defer connection.stream.close();
@@ -86,18 +104,13 @@ 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);
// const w = &writer.interface;
// try shared.protocol.sendHello(w, .{ .msg = "Hello from server!" });
try chunk.spawn(&the_chunk, shared.entity.Elf, allocator, .{
.pos = zm.f32x4(1, 1, 0, 0),
.hp = 10,
});
// try chunk.spawn(&the_chunk, shared.entity.Monster, allocator, .{
// .pos = zm.f32x4(1, 1, 0, 0),
// .hp = 20,
@@ -108,10 +121,54 @@ pub fn main() !void {
// .vel = zm.f32x4(0.2, 0, 0, 0),
// }, w);
shared.chunk.updateChunk(&the_chunk);
while (accumulator > dt * k) {
the_chunk.update(dt * k);
accumulator -= dt * k;
gt += dt * k;
}
try chunk.broadcastChanges(&the_chunk, allocator, host);
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;
}
}
fn on_connect(allocator: std.mem.Allocator, host: *enet.ENetHost, peer: *enet.ENetPeer, the_chunk: *shared.chunk.Chunk) !void {
var aw = try std.ArrayList(u8).initCapacity(allocator, 128);
defer aw.deinit(allocator);
_ = peer;
var writer = shared.bits.BitWriter.init(&aw);
for (the_chunk.entities.items) |e| {
const msg = shared.protocol_v1.makeMessage(.spawn_entity, 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);
}
//fn handle_connection(connection: std.net.Server.Connection) !void {}
// test "simple test" {
+286
View File
@@ -0,0 +1,286 @@
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),
bit_offset: usize = 0,
pub fn init(bytes: *std.ArrayList(u8)) BitWriter {
return .{ .bytes = bytes };
}
pub fn serialize(self: *BitWriter, allocator: std.mem.Allocator, value: anytype) !void {
const T = @TypeOf(value);
const info = @typeInfo(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));
var remaining : usize = bit_width;
while (remaining > 0) {
const byte_index = self.bit_offset / 8;
const bit_in_byte = self.bit_offset % 8;
// If we are moving into a new byte, grow the ArrayList
if (byte_index >= self.bytes.items.len) {
try self.bytes.append(allocator, 0);
}
const space_in_byte = 8 - bit_in_byte;
const chunk_size = @min(remaining, space_in_byte);
const mask : u8 = std.math.shr(u8, 0xFF, 8 - chunk_size);
const bits : u8 = @truncate(std.math.shr(UnsignedT, bits_to_write, remaining - chunk_size) & mask);
self.bytes.items[byte_index] |= std.math.shl(u8, bits, space_in_byte - chunk_size);
self.bit_offset += chunk_size;
remaining -= chunk_size;
}
}
pub fn written(self: *BitWriter) []const u8 {
const bytes_used = (self.bit_offset + 7) / 8; // round up bits → bytes
return self.bytes.items[0..bytes_used];
}
};
pub const BitReader = struct {
bytes: []const u8,
bit_offset: usize = 0,
pub fn init(bytes: []const u8) BitReader {
return .{ .bytes = bytes };
}
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);
var remaining: usize = bit_width;
var result: UnsignedT = 0;
while (remaining > 0) {
const byte_index = self.bit_offset / 8;
const bit_in_byte = self.bit_offset % 8;
const space_in_byte = 8 - bit_in_byte;
const chunk_size = @min(remaining, space_in_byte);
const mask: u8 = std.math.shr(u8, 0xFF, 8 - chunk_size);
// Extract chunk MSB-first from current byte
const shift_from_byte = space_in_byte - chunk_size;
const bits: u8 = std.math.shr(u8, self.bytes[byte_index], shift_from_byte) & mask;
// Shift into result (from MSB side)
result |= std.math.shl(UnsignedT, @intCast(bits), remaining - chunk_size);
self.bit_offset += chunk_size;
remaining -= chunk_size;
}
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);
}
+3
View File
@@ -0,0 +1,3 @@
pub const enet = @cImport({
@cInclude("enet/enet.h");
});
+104 -77
View File
@@ -1,94 +1,121 @@
const std = @import("std");
const std = @import("std");
const entity = @import("entity.zig");
const misc = @import("misc.zig");
const misc = @import("misc.zig");
pub fn Storage(comptime T: type) type {
return struct {
items: std.ArrayList(T),
// pub 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 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 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 update(self: *@This()) void {
// for (self.items.items, 0..) |*item, i| {
// _ = i;
// if (@hasDecl(T, "update"))
// item.update();
// }
// }
// };
// }
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 = Storage(T),
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(Storage(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;
switch (@typeInfo(Chunk)) {
.@"struct" => |s| {
inline for (s.fields) |field| {
const StorageT = field.type;
@field(chunk, field.name) = try StorageT.init(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);
}
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 update(self: *@This(), dt: f32) void {
for (self.entities.items) |e| {
switch (e) {
.Soldier => |s| {
s.update(dt);
},
.Alien => |a| {
a.update(dt);
},
}
},
else => unreachable,
}
}
}
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,
}
}
};
+87 -27
View File
@@ -1,43 +1,103 @@
const std = @import("std");
const zm = @import("zmath");
const protocol = @import("protocol.zig");
const std = @import("std");
const zm = @import("zmath");
const misc = @import("misc.zig");
pub const id = u64;
pub const INVALID_ID: id = 0;
pub const EntityKinds = .{
Elf,
Monster,
Projectile,
Soldier,
Alien,
};
pub const Elf = struct {
const Entity = struct {
};
pub const Soldier = struct {
id: id = INVALID_ID,
pos: 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: *Elf) void {
self.pos += zm.f32x4(1, 1, 0, 0);
}
pub fn to_message(self: *Elf) protocol.Elf_v1 {
return protocol.Elf_v1.init(self);
pub fn update(self: *@This(), dt: f32) void {
self.pos = self.pos + self.vel * zm.splat(zm.Vec, dt);
}
};
pub const Monster = struct {
pub const Alien = struct {
id: id = INVALID_ID,
pos: zm.Vec,
hp: i32,
};
hp: i32 = 0,
pos: zm.Vec = .{0, 0, 0, 0},
vel: zm.Vec = .{0, 0, 0, 0},
pub const Projectile = struct {
id: id = INVALID_ID,
pos: zm.Vec,
vel: zm.Vec,
pub fn update(self: *Projectile) void {
self.pos = self.pos + self.vel;
std.log.info("pos=({})", .{self.pos});
pub fn update(self: *@This(), dt: f32) void {
self.pos = self.pos + self.vel * zm.splat(zm.Vec, dt);
}
};
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,
.alignment = @alignOf(*T),
};
}
return @Type(.{
.@"union" = .{
.layout = .auto,
.fields = &fields,
.decls = &.{},
.tag_type = Tag,
},
});
}
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));
}
-146
View File
@@ -1,146 +0,0 @@
const std = @import("std");
const zm = @import("zmath");
const bufzilla = @import("bufzilla");
const entity = @import("entity.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) {
elf_v1: Elf_v1,
monster_v1: Monster_v1,
projectile_v1: Projectile_v1,
};
pub fn makeSpawnMessage(comptime T: type, e: T) Message {
if (T == entity.Elf) {
return .{ .spawn_entity = .{ .elf_v1 = Elf_v1.init(e) } };
} else if (T == entity.Monster) {
return .{ .spawn_entity = .{ .monster_v1 = Monster_v1.init(e) } };
} else if (T == entity.Projectile) {
return .{ .spawn_entity = .{ .projectile_v1 = Projectile_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 Elf_v1 = struct {
id: entity.id = entity.INVALID_ID,
pos: zm.Vec,
hp: i32,
pub fn init(elf: entity.Elf) Elf_v1 {
return .{
.id = elf.id,
.pos = elf.pos,
.hp = elf.hp,
};
}
pub fn decode(r: *bufzilla.Reader) !Elf_v1 {
_ = r;
return .{
// .id = try r.readInt(u64, .little),
// .pos = try readVec4(r),
// .vel = try readVec4(r),
};
}
};
pub const Monster_v1 = struct {
id: entity.id = entity.INVALID_ID,
pos: zm.Vec,
hp: i32,
pub fn init(monster: entity.Monster) Monster_v1 {
return .{
.id = monster.id,
.pos = monster.pos,
.hp = monster.hp,
};
}
// pub fn encode(self: Monster_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) !Monster_v1 {
return .{
.id = try r.readInt(u64, .little),
.pos = try readVec4(r),
.vel = try readVec4(r),
};
}
};
pub const Projectile_v1 = struct {
id: entity.id = entity.INVALID_ID,
pos: zm.Vec,
vel: zm.Vec,
pub fn init(projectile: entity.Projectile) Projectile_v1 {
return .{
.id = projectile.id,
.pos = projectile.pos,
.vel = projectile.vel,
};
}
// pub fn encode(self: Projectile_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) !Projectile_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);
}
+109
View File
@@ -0,0 +1,109 @@
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;
const MessageKind = enum(c_char) {
spawn_entity,
update_entity,
despawn_entity,
};
pub const Message = union(MessageKind) {
spawn_entity: EntityPayload,
update_entity: EntityPayload,
despawn_entity: EntityPayload,
};
pub fn makeMessage(kind: MessageKind, e: anytype) Message {
const payload = payloadFromEntity(e);
return switch (kind) {
.spawn_entity => .{ .spawn_entity = payload },
.update_entity => .{ .update_entity = payload },
.despawn_entity => .{ .despawn_entity = payload },
};
}
pub fn payloadFromEntity(e: entity.Ref) EntityPayload {
return switch (e) {
.Soldier => |s| .{ .soldier = Soldier.init(s.*) },
.Alien => |a| .{ .alien = Alien.init(a.*) },
};
}
const EntityKind = enum(c_char) {
soldier,
alien,
};
pub const EntityPayload = union(EntityKind) {
soldier: Soldier,
alien: Alien,
};
pub const Soldier = struct {
id: entity.id = entity.INVALID_ID,
hp: i32,
pos: zm.Vec,
vel: zm.Vec,
pub fn init(soldier: entity.Soldier) Soldier {
return .{
.id = soldier.id,
.hp = soldier.hp,
.pos = soldier.pos,
.vel = soldier.vel,
};
}
};
pub const Alien = struct {
id: entity.id = entity.INVALID_ID,
hp: i32,
pos: zm.Vec,
pub fn init(alien: entity.Alien) Alien {
return .{
.id = alien.id,
.pos = alien.pos,
.hp = alien.hp,
};
}
};
test "Protocol" {
std.debug.print("Protocol\n", .{});
const s = entity.Soldier{
.hp = 66,
.pos = .{1, 2, 3, 4},
.vel = .{5, 6, 7, 8},
};
const msg = makeMessage(.spawn_entity, s);
const allocator = std.testing.allocator;
var aw = try std.ArrayList(u8).initCapacity(allocator, 128);
defer aw.deinit(allocator);
var writer = bits.BitWriter.init(&aw);
try writer.serialize(allocator, msg);
var reader = bits.BitReader.init(writer.written());
const recv = try reader.deserialize(allocator, Message);
std.debug.print("msg: {}\n", .{msg});
std.debug.print("recv: {}\n", .{recv});
try std.testing.expectEqual(msg, recv);
}
+10 -4
View File
@@ -1,4 +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 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());
}
+30 -1
View File
@@ -1,6 +1,6 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -15,6 +15,24 @@ pub fn build(b: *std.Build) void {
}),
});
var flags = try std.ArrayList([]const u8).initCapacity(b.allocator, 0);
switch (target.result.os.tag) {
.linux, .macos => {
try flags.append(b.allocator, "-DHAS_FCNTL=1");
try flags.append(b.allocator, "-DHAS_POLL=1");
try flags.append(b.allocator, "-DHAS_GETADDRINFO=1");
try flags.append(b.allocator, "-DHAS_GETNAMEINFO=1");
try flags.append(b.allocator, "-DHAS_INET_PTON=1");
try flags.append(b.allocator, "-DHAS_INET_NTOP=1");
try flags.append(b.allocator, "-DHAS_MSGHDR_FLAGS=1");
},
else => {},
}
try flags.append(b.allocator, "-DHAS_OFFSETOF=1");
try flags.append(b.allocator, "-DHAS_SOCKLEN_T=1");
lib.root_module.addCSourceFiles(.{
.root = enet_src.path(""),
.files = &[_][]const u8{
@@ -28,6 +46,17 @@ pub fn build(b: *std.Build) void {
"unix.c",
"win32.c",
},
.flags = &[_][]const u8{
"-DHAS_OFFSETOF=1",
"-DHAS_SOCKLEN_T=1",
"-DHAS_FCNTL=1",
"-DHAS_POLL=1",
"-DHAS_GETADDRINFO=1",
"-DHAS_GETNAMEINFO=1",
"-DHAS_INET_PTON=1",
"-DHAS_INET_NTOP=1",
"-DHAS_MSGHDR_FLAGS=1",
}
});
+1
View File
@@ -1,5 +1,6 @@
.{
.name = .enet,
.fingerprint = 0x201714c0933cd1fc,
.version = "0.1.0",
.minimum_zig_version = "0.15.2",
.paths = .{
+1
View File
@@ -1,5 +1,6 @@
.{
.name = .freetype,
.fingerprint = 0xac2059b69e282536,
.version = "0.1.0",
.minimum_zig_version = "0.15.2",
.paths = .{
+2 -2
View File
@@ -12,8 +12,8 @@
},
.dependencies = .{
.tracy_src = .{
.url = "https://github.com/wolfpld/tracy/archive/master.tar.gz",
.hash = "N-V-__8AAGduOgHtud2AEkqZakMCYUnhwZjnN8MIHZV0TYMJ",
.url = "https://github.com/wolfpld/tracy/archive/8f0c83dbf90faa982624e005ce76ff0fd5c77153.zip",
.hash = "N-V-__8AAAF7OgG5ck73SkEmLbQPExC4jddnsDCAMtBAIBXy",
},
},
}