This commit is contained in:
Vicente Ferrari Smith 2026-02-08 15:22:31 +01:00
parent 29c136d34c
commit 5a79ae71c0
12 changed files with 531 additions and 385 deletions

2
.vscode/tasks.json vendored
View File

@ -17,7 +17,7 @@
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
"clear": true,
},
"problemMatcher": [],
"group": {

View File

@ -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.

View File

@ -1,4 +1,5 @@
const std = @import("std");
const std = @import("std");
const shared = @import("shared");
const ConnectState = union(enum) {
disconnected,

View File

@ -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);

View File

@ -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);
}

View File

@ -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 {}

View File

@ -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);
}

View File

@ -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);
},
}
}
}
}
};

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -1,5 +1,6 @@
.{
.name = .freetype,
.fingerprint = 0xac2059b69e282536,
.version = "0.1.0",
.minimum_zig_version = "0.15.2",
.paths = .{