engine transport interface

This commit is contained in:
peterino2 2025-09-28 22:30:19 -07:00
parent 43d88b76e9
commit f012815d93
9 changed files with 353 additions and 12 deletions

View File

@ -235,9 +235,9 @@ pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []
} }
pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void { pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void {
if (self.staticBuild) { //if (self.staticBuild) {
return; //return;
} // }
inline for (DynamicDepList) |d| { inline for (DynamicDepList) |d| {
b.installArtifact(b.dependency( b.installArtifact(b.dependency(
@ -258,6 +258,7 @@ const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } =
.{ .dep = "lua", .artifact = "luac" }, .{ .dep = "lua", .artifact = "luac" },
.{ .dep = "miniaudio", .artifact = "miniaudio_c" }, .{ .dep = "miniaudio", .artifact = "miniaudio_c" },
.{ .dep = "zphysics", .artifact = "joltc" }, .{ .dep = "zphysics", .artifact = "joltc" },
.{ .dep = "enet", .artifact = "enet_c" },
.{ .dep = "ozz", .artifact = "ozz_cpp" }, .{ .dep = "ozz", .artifact = "ozz_cpp" },
}; };

View File

@ -20,6 +20,7 @@
.zphysics = .{ .path = "lib/zphysics" }, .zphysics = .{ .path = "lib/zphysics" },
.sdl3 = .{ .path = "lib/sdl3" }, .sdl3 = .{ .path = "lib/sdl3" },
.enet = .{ .path = "lib/enet" },
}, },
.paths = .{ .paths = .{
"", "",

View File

@ -159,6 +159,7 @@ pub const Engine = struct {
core.engine_err("RECURSIVE OBJECT CREATION NOT ALLOWED", .{}); core.engine_err("RECURSIVE OBJECT CREATION NOT ALLOWED", .{});
return error.BadInit; return error.BadInit;
} }
self.engineObjectLUF = self.frameNumber; // bump this every time an engine object mutation has happened self.engineObjectLUF = self.frameNumber; // bump this every time an engine object mutation has happened
self.createObjectLock = true; self.createObjectLock = true;
defer self.createObjectLock = false; defer self.createObjectLock = false;
@ -173,6 +174,9 @@ pub const Engine = struct {
try self.engineObjects.append(self.allocator, newObjectRef); try self.engineObjects.append(self.allocator, newObjectRef);
if (vtable.singletonName) |singletonName| { if (vtable.singletonName) |singletonName| {
if (self.engineObjectsByName.contains(vtable.singletonName.?)) {
return error.DuplicateEngineObject;
}
try self.engineObjectsByName.put(self.allocator, singletonName, newObjectRef); try self.engineObjectsByName.put(self.allocator, singletonName, newObjectRef);
} }

View File

@ -0,0 +1,210 @@
pub var TransportInterfaceVTable = net.TransportInterface.Implement(@This());
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.ENetTransport");
allocator: std.mem.Allocator,
peerType: net.PeerType = .offline,
address: ?enet.ENetAddress = null,
host: ?*enet.ENetHost = null,
clients: std.ArrayListUnmanaged(net.ClientInfo) = .{},
const MAX_CLIENTS = 128;
const DEFAULT_PORT = 7777;
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
try enet_mod.initialize();
return self;
}
pub fn stopServer(self: *@This()) void {
// other shit
self.peerType = .offline;
}
pub fn startClient(self: *@This(), target: []const u8) !void {
if (self.peerType != .offline) {
net.err("unable to start client, already in a different mode {s}", .{@tagName(self.peerType)});
return error.BadInit;
}
// Create client host
self.host = enet.enet_host_create(null, 1, 2, 0, 0) orelse {
return error.BadInit;
};
self.peerType = .client;
// Connect to target will be handled separately
_ = target;
}
pub fn startServer(self: *@This(), bindInfo: ?[]const []const u8) !void {
if (self.peerType != .offline) {
net.err("unable to start server, already in a different mode {s}", .{@tagName(self.peerType)});
return;
}
const port: u16 = try std.fmt.parseInt(u16, bindInfo.?[0], 0);
self.address = enet.ENetAddress{
.host = enet.ENET_HOST_ANY,
.port = port,
};
self.host = enet.enet_host_create(&self.address.?, 128, 2, 0, 0) orelse {
return error.ServerStartFailed;
};
net.log("server started hosting on port {d}", .{port});
self.peerType = .server;
}
pub fn connect(self: *@This(), target: []const u8) !void {
var i: isize = @as(isize, @intCast(target.len)) - 1;
var port: u16 = DEFAULT_PORT;
var targetStr: ?[]const u8 = target;
var portString: ?[]const u8 = null;
while (i > 0) : (i -= 1) {
if (target[@as(usize, @intCast(i))] == ':') {
portString = target[@as(usize, @intCast(i)) + 1 .. target.len];
targetStr = target[0..@as(usize, @intCast(i))];
break;
}
}
if (portString) |ps| {
port = try std.fmt.parseInt(u16, ps, 0);
}
var buf: [256]u8 = undefined;
const targetName = try std.fmt.bufPrintZ(&buf, "{s}", .{targetStr.?});
self.address = enet.ENetAddress{
.host = 0,
.port = port,
};
_ = enet.enet_address_set_host(&self.address.?, targetName);
}
pub fn sendMessage(self: *@This(), connectionId: u32, data: []const u8, reliable: bool) !void {
_ = self;
_ = connectionId;
_ = data;
_ = reliable;
// TODO: Implement message sending
return error.UnknownStatePanic;
}
pub fn deinitialize(self: *@This()) void {
if (self.host) |host| {
enet.enet_host_destroy(host);
self.host = null;
}
self.clients.deinit(self.allocator);
enet_mod.deinitialize();
}
pub fn preTick(self: *@This(), dt: f64) !void {
switch (self.peerType) {
.server => {
self.tickServer(dt);
},
.client => {
self.tickClient(dt);
},
else => {},
}
}
pub fn tickServer(self: *@This(), dt: f64) void {
_ = dt;
var event: enet.ENetEvent = undefined;
const result = enet.enet_host_service(self.host.?, &event, 0);
if (result > 0) {
switch (event.type) {
enet.ENET_EVENT_TYPE_CONNECT => {
// should add to clientsWaiting,
self.clients.append(self.allocator, .{
.address = net.ip2String(self.allocator, event.peer.*.address.host) catch unreachable,
.port = event.peer.*.address.port,
.id = event.peer.*.connectID,
.data = event.peer,
}) catch unreachable;
// Send server connection info + challenge
// const welcome_msg = "Welcome to ENet test server!";
// if (enet.enet_packet_create(welcome_msg.ptr, welcome_msg.len, enet.ENET_PACKET_FLAG_RELIABLE)) |packet| {
//_ = enet.enet_peer_send(event.peer, 0, packet);
//}
},
enet.ENET_EVENT_TYPE_RECEIVE => {
const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength];
_ = data;
// std.debug.print("Received message #{}: '{s}'\n", .{ message_count, data });
// Echo the message back to the client
// const echo_msg = std.fmt.allocPrint(std.heap.page_allocator, "Echo: {s}", .{data}) catch "Echo: (allocation failed)";
// defer if (!std.mem.eql(u8, echo_msg, "Echo: (allocation failed)")) std.heap.page_allocator.free(echo_msg);
// if (enet.enet_packet_create(echo_msg.ptr, echo_msg.len, enet.ENET_PACKET_FLAG_RELIABLE)) |packet| {
//_ = enet.enet_peer_send(event.peer, 0, packet);
//}
// Destroy the received packet
enet.enet_packet_destroy(event.packet);
// If this is a "quit" message, stop the server
// if (std.mem.eql(u8, data, "quit")) {
// std.debug.print("Received quit command, shutting down server...\n", .{});
// }
},
enet.ENET_EVENT_TYPE_DISCONNECT => {
for (self.clients.items, 0..) |client, i| {
if (client.id == event.peer.*.connectID) {
_ = self.clients.swapRemove(i);
}
}
// event.peer.*.data = null;
},
else => {},
}
} else if (result < 0) {
net.err("Error servicing host\n", .{});
}
}
pub fn tickClient(self: *@This(), dt: f64) void {
_ = self;
_ = dt;
}
pub fn destroy(self: *@This()) void {
if (self.host) |host| {
enet.enet_host_destroy(host);
}
enet_mod.deinitialize();
self.allocator.destroy(self);
}
const core = @import("core");
const net = @import("../net.zig");
const std = @import("std");
const enet_mod = @import("enet");
const enet = enet_mod.c;

View File

@ -3,10 +3,10 @@ const std = @import("std");
const netEngine = @import("netEngine.zig"); const netEngine = @import("netEngine.zig");
pub const NetEngine = netEngine.NetEngine; pub const NetEngine = netEngine.NetEngine;
pub const net_err = netEngine.net_err; pub const err = netEngine.net_err;
pub const net_errs = netEngine.net_errs; pub const errs = netEngine.net_errs;
pub const net_log = netEngine.net_log; pub const log = netEngine.net_log;
pub const net_logs = netEngine.net_logs; pub const logs = netEngine.net_logs;
pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void { pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void {
_ = args; _ = args;
@ -17,7 +17,8 @@ pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.me
return; return;
} }
_ = core.createObject(NetEngine, .{ .can_tick = true }) catch unreachable; const engine = try core.createObject(NetEngine, .{ .can_tick = true });
try engine.initalizeTransport(netEngine.EnetTransport);
} }
pub fn shutdown_module(allocator: std.mem.Allocator) void { pub fn shutdown_module(allocator: std.mem.Allocator) void {
@ -31,3 +32,98 @@ pub const Module = core.ModuleDescription{
.name = "net", .name = "net",
.enabledByDefault = false, .enabledByDefault = false,
}; };
pub const PeerType = enum {
offline,
server,
client,
};
pub const TransportType = enum {
enet,
};
pub const TransportError = error{
UnknownStatePanic,
BadInit,
UnknownError,
OutOfMemory,
ServerStartFailed,
};
pub const ClientInfo = struct {
id: u32,
port: u16,
address: []const u8,
data: ?*anyopaque,
};
pub fn ip2String(allocator: std.mem.Allocator, ip: u32) ![:0]u8 {
return try std.fmt.allocPrintZ(allocator, "{d}.{d}.{d}.{d}", .{
(ip >> 0) & 0xFF,
(ip >> 8) & 0xFF,
(ip >> 16) & 0xFF,
(ip >> 24) & 0xFF,
});
}
pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", struct {
startServer: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!void,
stopServer: *const fn (*anyopaque) void,
startClient: *const fn (*anyopaque, target: []const u8) TransportError!void,
connect: *const fn (*anyopaque, target: []const u8) TransportError!void,
sendMessage: *const fn (*anyopaque, connectionId: u32, data: []const u8, reliable: bool) TransportError!void,
deinitialize: *const fn (*anyopaque) void,
pub fn Implement(comptime T: type) @This() {
const Wrap = struct {
pub fn startServer(p: *anyopaque, bindInfo: ?[]const []const u8) TransportError!void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.startServer(bindInfo) catch return TransportError.ServerStartFailed;
}
pub fn stopServer(p: *anyopaque) void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.stopServer();
}
pub fn startClient(p: *anyopaque, target: []const u8) TransportError!void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.startClient(target) catch return TransportError.UnknownStatePanic;
}
pub fn connect(p: *anyopaque, target: []const u8) TransportError!void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.connect(target) catch return TransportError.UnknownStatePanic;
}
pub fn sendMessage(p: *anyopaque, connectionId: u32, data: []const u8, reliable: bool) TransportError!void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.sendMessage(connectionId, data, reliable) catch return TransportError.UnknownStatePanic;
}
pub fn deinitialize(p: *anyopaque) void {
const ptr: *T = @ptrCast(@alignCast(p));
ptr.deinitialize();
}
};
inline for (@typeInfo(Wrap).@"struct".decls) |d| {
if (!@hasDecl(T, d.name)) {
@compileError(@typeName(T) ++ " is missing implementation of func " ++ d.name);
}
}
return .{
.startServer = Wrap.startServer,
.stopServer = Wrap.stopServer,
.startClient = Wrap.startClient,
.connect = Wrap.connect,
.sendMessage = Wrap.sendMessage,
.deinitialize = Wrap.deinitialize,
};
}
});

View File

@ -1,5 +1,7 @@
const core = @import("core"); const core = @import("core");
const net = @import("net.zig");
const std = @import("std"); const std = @import("std");
pub const EnetTransport = @import("enet/EnetTransport.zig");
pub fn net_log(comptime fmt: []const u8, args: anytype) void { pub fn net_log(comptime fmt: []const u8, args: anytype) void {
core.printInner("[NET ]: " ++ fmt ++ "\n", args); core.printInner("[NET ]: " ++ fmt ++ "\n", args);
@ -21,6 +23,7 @@ pub const NetEngine = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.NetEngine"); pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.NetEngine");
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
transport: ?core.Reference(net.TransportInterface) = null,
pub fn init(allocator: std.mem.Allocator) !*@This() { pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This()); const self = try allocator.create(@This());
@ -28,10 +31,25 @@ pub const NetEngine = struct {
.allocator = allocator, .allocator = allocator,
}; };
net_logs("NetEngine initialized");
return self; return self;
} }
pub fn initalizeTransport(self: *@This(), comptime T: type) !void {
const transport = try core.createObject(T, .{ .can_tick = true });
self.transport = core.Reference(net.TransportInterface){
.ptr = transport,
.vtable = T.TransportInterfaceVTable,
};
net.log("transport initialized {s}", .{@typeName(T)});
}
pub fn startServer(self: *@This(), bindInfo: ?[]const []const u8) !void {
if (self.transport) |transport| {
try transport.vtable.startServer(transport.ptr, bindInfo);
}
}
pub fn shutdown(self: *@This()) void { pub fn shutdown(self: *@This()) void {
net_logs("NetEngine shutting down"); net_logs("NetEngine shutting down");
_ = self; _ = self;

7
lib/enet/build.zig vendored
View File

@ -4,10 +4,13 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false;
_ = static_build; // enet is always built as static library
// am i good to always have enet as static? // am i good to always have enet as static?
const enet = b.addStaticLibrary(.{ const enet = if (static_build and false) b.addStaticLibrary(.{
.name = "enet_c",
.target = target,
.optimize = optimize,
}) else b.addSharedLibrary(.{
.name = "enet_c", .name = "enet_c",
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,

View File

@ -28,6 +28,8 @@
.sdl3 = .{ .path = "../lib/sdl3" }, .sdl3 = .{ .path = "../lib/sdl3" },
.miniaudio = .{ .path = "../lib/miniaudio" }, .miniaudio = .{ .path = "../lib/miniaudio" },
.lua = .{ .path = "../lib/lua" }, .lua = .{ .path = "../lib/lua" },
.enet = .{ .path = "../lib/enet" },
}, },
.paths = .{ .paths = .{
"", "",

View File

@ -6,6 +6,7 @@ pub export fn startup(p_allocator: *anyopaque, p_a: ?*anyopaque) bool {
sys.setupFromModule(); sys.setupFromModule();
rend.setupFromModule(); rend.setupFromModule();
backlog.physics.setupFromModule(); backlog.physics.setupFromModule();
// backlog.net.setupFromModule();
// TODO backlog.setupFromModule // TODO backlog.setupFromModule
core.engine_logs("creating externgame"); core.engine_logs("creating externgame");
@ -387,6 +388,10 @@ pub const ExternGameObject = struct {
if (ig.sliderFloat("sliderFloat", &self.volume, 0, 100, null, .{})) { if (ig.sliderFloat("sliderFloat", &self.volume, 0, 100, null, .{})) {
audio.context().setVolume(self.volume / 100); audio.context().setVolume(self.volume / 100);
} }
if (ig.smallButton("host")) {
core.get(net.NetEngine).startServer(&.{"7777"}) catch unreachable;
}
} }
ig.end(); ig.end();
@ -429,6 +434,7 @@ const rend = backlog.rend;
const ig = imgui.api; const ig = imgui.api;
const sys = backlog.sys; const sys = backlog.sys;
const ui = backlog.ui; const ui = backlog.ui;
const net = backlog.net;
const audio = backlog.audio; const audio = backlog.audio;
const platform = backlog.platform; const platform = backlog.platform;
const extras = @import("gameExtras"); const extras = @import("gameExtras");