diff --git a/engine/core/src/core.zig b/engine/core/src/core.zig index bf41c37..21b4fed 100644 --- a/engine/core/src/core.zig +++ b/engine/core/src/core.zig @@ -453,3 +453,8 @@ pub fn SlackStruct(comptime T: type, comptime SlackSize: usize) type { pub fn cast(comptime T: type, p: *anyopaque) T { return @alignCast(@ptrCast(p)); } + +pub fn MakeNameFmt(comptime fmt: []const u8, args: anytype) Name { + var makeNameBuf: [256]u8 = undefined; + return algorithm.MakeName(std.fmt.bufPrint(&makeNameBuf, fmt, args) catch unreachable); +} diff --git a/engine/net/src/enet/EnetTransport.zig b/engine/net/src/enet/EnetTransport.zig index dbb9dec..b057777 100644 --- a/engine/net/src/enet/EnetTransport.zig +++ b/engine/net/src/enet/EnetTransport.zig @@ -2,15 +2,113 @@ 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) = .{}, +sessions: std.ArrayListUnmanaged(*net.Session) = .{}, +deadSessions: std.ArrayListUnmanaged(*net.Session) = .{}, const MAX_CLIENTS = 128; const DEFAULT_PORT = 7777; +const ConnectionTimeout = 10000; + +const UnreliableChannel = 0; +const ReliableChannel = 1; +const ChannelCount = 1; + +// to implement the new links and sessions interface. +// +const QueuedLinkData = struct { + bytes: []u8, + reliable: bool, +}; + +const ENetLinkData = struct { + peer: [*c]enet.ENetPeer, + link: *net.Link, + + // todo- split into reliable and unreliable + // also maybe move to the upper level net.Link instead of LinkData + queuedMessages: core.RingQueueU(QueuedLinkData), + + pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void { + self.queuedMessages.deinit(); + allocator.destroy(self); + } + + pub fn queuePacketData(self: *@This(), bytes: []const u8, reliable: bool) void { + const allocator = net.netAllocator(); + self.queuedMessages.push(.{ + .bytes = allocator.dupe(u8, bytes) catch unreachable, + .reliable = reliable, + }) catch unreachable; + } + + // raw access to sendPacket + pub fn sendPacket(self: *@This(), bytes: []const u8, reliable: bool) void { + if (enet.enet_packet_create(bytes.ptr, bytes.len, if (reliable) enet.ENET_PACKET_FLAG_RELIABLE else enet.ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)) |packet| { + _ = enet.enet_peer_send(self.peer, if (reliable) 0 else 1, packet); + } + } +}; + +const ENetTransport = @This(); + +const ENetSessionData = struct { + address: enet.ENetAddress, + host: [*c]enet.ENetHost, + session: *net.Session, + messageCount: u32 = 0, + + pub fn findLinkByPeer(self: *@This(), peer: [*c]enet.ENetPeer) ?*net.Link { + for (self.session.links.items) |link| { + if (getLinkData(link).peer == peer) { + return link; + } + } + return null; + } + + pub fn tick(self: *@This()) void { + var event: enet.ENetEvent = undefined; + + // Service the host with a 1000ms timeout + const result = enet.enet_host_service(self.host, &event, 0); + + if (result > 0) { + switch (event.type) { + enet.ENET_EVENT_TYPE_CONNECT => { + core.engine_log("a client connected, creating link", .{}); + const welcome_msg = "Welcome to ENet test server!"; + const link = core.get(ENetTransport).createLink(self.session) catch unreachable; + const linkData = getLinkData(link); + linkData.peer = event.peer; + linkData.sendPacket(welcome_msg, true); + }, + enet.ENET_EVENT_TYPE_RECEIVE => { + self.messageCount += 1; + const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength]; + core.engine_log("Received message #{}: '{s}' echoing it back", .{ self.messageCount, data }); + + if (self.findLinkByPeer(event.peer)) |link| { + const linkData = getLinkData(link); + linkData.queuePacketData(data, true); + } + + // Destroy the received packet + enet.enet_packet_destroy(event.packet); + }, + else => {}, + } + } + + for (self.session.links.items) |link| { + const linkData = getLinkData(link); + + while (linkData.queuedMessages.pop()) |queuedData| { + linkData.sendPacket(queuedData.bytes, queuedData.reliable); + net.netAllocator().free(queuedData.bytes); + } + } + } +}; pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); @@ -24,106 +122,216 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { 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; +fn createSession(self: *@This(), address: ?enet.ENetAddress) !*net.Session { + const session = try net.netAllocator().create(net.Session); + session.* = .{ + .transportName = "ENet", + .transport = .{ .vtable = TransportInterfaceVTable, .ptr = self }, + .allocator = net.netAllocator(), }; - self.peerType = .client; + const transportData = try net.netAllocator().create(ENetSessionData); + transportData.session = session; - // Connect to target will be handled separately - _ = target; + if (address != null) { + transportData.address = address.?; + transportData.host = enet.enet_host_create(&transportData.address, MAX_CLIENTS, 2, 0, 0) orelse { + return error.ServerStartFailed; + }; + net.log("host created on port {d}", .{address.?.port}); + } else { + transportData.host = enet.enet_host_create(null, 1, 2, 0, 0) orelse { + return error.ClientStartFailed; + }; + transportData.address = .{ + .host = 0, + .port = 0, + }; + net.log("client session created", .{}); + } + + session.transportData = transportData; + try self.sessions.append(self.allocator, session); + + return session; } -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; - } +pub fn hostSession(self: *@This(), bindInfo: ?[]const []const u8) !*net.Session { const port: u16 = try std.fmt.parseInt(u16, bindInfo.?[0], 0); - - self.address = enet.ENetAddress{ + const session = try self.createSession(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; + return session; } -pub fn connect(self: *@This(), target: []const u8) !void { - var i: isize = @as(isize, @intCast(target.len)) - 1; - var port: u16 = DEFAULT_PORT; +pub fn endSession(self: *@This(), session: *net.Session) void { + if (session.transportData) |transportData| { + const td = core.cast(*ENetSessionData, transportData); + enet.enet_host_destroy(td.host); + } - var targetStr: ?[]const u8 = target; - var portString: ?[]const u8 = null; + for (self.sessions.items, 0..) |s, i| { + if (s == session) { + _ = self.sessions.orderedRemove(i); + return; + } + } +} - 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))]; +pub fn printIp(ip: u32) void { + core.engine_log("{d}.{d}.{d}.{d}", .{ + (ip >> 0) & 0xFF, + (ip >> 8) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 24) & 0xFF, + }); +} + +pub fn parseConnectTarget(allocator: std.mem.Allocator, in: []const u8) !struct { + port: u16, + address: [:0]u8, +} { + var portStr: ?[]const u8 = null; + var base: []const u8 = in; + + var j: usize = in.len; + while (j > 0) : (j -= 1) { + const i = j - 1; + + if (in[i] == ':') { + portStr = in[j..in.len]; + base = in[0..i]; break; } } - if (portString) |ps| { - port = try std.fmt.parseInt(u16, ps, 0); + var p: u16 = DEFAULT_PORT; + + if (portStr) |ps| { + p = try std.fmt.parseInt(u16, ps, 10); } - var buf: [256]u8 = undefined; - const targetName = try std.fmt.bufPrintZ(&buf, "{s}", .{targetStr.?}); + return .{ .port = p, .address = try std.fmt.allocPrintZ(allocator, "{s}", .{base}) }; +} - self.address = enet.ENetAddress{ - .host = 0, - .port = port, +pub fn createLink(self: *@This(), session: *net.Session) !*net.Link { + const newLink = try net.netAllocator().create(net.Link); + + newLink.* = .{ + .transport = .{ .vtable = TransportInterfaceVTable, .ptr = self }, + .allocator = net.netAllocator(), + }; + // errdefer newLink.destroy(); + + const linkInfo = try net.netAllocator().create(ENetLinkData); + errdefer self.allocator.destroy(linkInfo); + + linkInfo.* = .{ + .link = newLink, + .peer = null, + .queuedMessages = try core.RingQueueU(QueuedLinkData).init(net.netAllocator(), 4092), }; - _ = enet.enet_address_set_host(&self.address.?, targetName); + newLink.transportData = linkInfo; + newLink.session = session; + + try session.addLink(newLink); + + return newLink; } -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 getSessionData(session: *net.Session) *ENetSessionData { + return core.cast(*ENetSessionData, session.transportData.?); } -pub fn deinitialize(self: *@This()) void { - if (self.host) |host| { - enet.enet_host_destroy(host); - self.host = null; +pub fn getLinkData(link: *net.Link) *ENetLinkData { + return core.cast(*ENetLinkData, link.transportData.?); +} + +// creates a session and a link with that session +pub fn connect(self: *@This(), target: []const u8) !*net.Link { + const rv = try parseConnectTarget(self.allocator, target); + defer self.allocator.free(rv.address); + + // parse address to target + const session = try self.createSession(null); + errdefer { + session.skipEndSession = true; + session.destroy(); } - self.clients.deinit(self.allocator); - enet_mod.deinitialize(); + + const newLink = try self.createLink(session); + const linkData = core.cast(*ENetLinkData, newLink.transportData.?); + + // Set up server address + const sessionInfo = getSessionData(session); + + // Resolve server hostname + if (enet.enet_address_set_host(&sessionInfo.address, rv.address.ptr) != 0) { + std.debug.print("Failed to resolve server address: {s}\n", .{rv.address}); + return error.AddressResolutionFailed; + } + + sessionInfo.address.port = rv.port; + + // Connect to server + linkData.peer = enet.enet_host_connect(sessionInfo.host, &sessionInfo.address, ChannelCount, 0); + if (linkData.peer == null) { + std.debug.print("Failed to create connection to server\n", .{}); + return error.ConnectionFailed; + } + + std.debug.print("connecting to server [{s}] [{d}]\n", .{ rv.address, rv.port }); + var event: enet.ENetEvent = undefined; + if (enet.enet_host_service(sessionInfo.host.?, &event, ConnectionTimeout) > 0 and event.type == enet.ENET_EVENT_TYPE_CONNECT) { + std.debug.print("Connected to server successfully!\n", .{}); + newLink.state = .connecting; + newLink.linkType = .client; + newLink.session = session; + } else { + std.debug.print("Failed to connect to server within timeout event.type {d} \n", .{event.type}); + enet.enet_peer_reset(linkData.peer); + return error.ConnectionTimeout; + } + + return newLink; +} + +pub fn endLink(self: *@This(), link: *net.Link) void { + const linkData = getLinkData(link); + + enet.enet_peer_reset(linkData.peer); + + net.netAllocator().destroy(linkData); + self.deadSessions.append(self.allocator, link.session.?) catch {}; +} + +pub fn sendMessageLink(self: *@This(), link: *net.Link, data: []const u8, reliable: bool) !void { + _ = self; + + const linkData = getLinkData(link); + linkData.queuePacketData(data, reliable); } pub fn preTick(self: *@This(), dt: f64) !void { - switch (self.peerType) { - .server => { - self.tickServer(dt); - }, - .client => { - self.tickClient(dt); - }, - else => {}, + //switch (self.peerType) { + // .server => { + // self.tickServer(dt); + // }, + // .client => { + // self.tickClient(dt); + // }, + //else => {}, + // } + _ = dt; + + for (self.sessions.items) |session| { + const sessionInfo = getSessionData(session); + sessionInfo.tick(); + + // tick messages in here } } @@ -137,41 +345,12 @@ pub fn tickServer(self: *@This(), dt: f64) void { 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); - //} + core.engine_log("client connected!!!!"); }, - 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 => { @@ -180,7 +359,6 @@ pub fn tickServer(self: *@This(), dt: f64) void { _ = self.clients.swapRemove(i); } } - // event.peer.*.data = null; }, else => {}, @@ -196,9 +374,12 @@ pub fn tickClient(self: *@This(), dt: f64) void { } pub fn destroy(self: *@This()) void { - if (self.host) |host| { - enet.enet_host_destroy(host); + for (self.sessions.items) |session| { + session.destroy(); } + self.sessions.deinit(self.allocator); + self.deadSessions.deinit(self.allocator); + enet_mod.deinitialize(); self.allocator.destroy(self); } diff --git a/engine/net/src/net.zig b/engine/net/src/net.zig index 23e36b5..dfbdceb 100644 --- a/engine/net/src/net.zig +++ b/engine/net/src/net.zig @@ -1,6 +1,7 @@ const core = @import("core"); const std = @import("std"); +const net = @import("net.zig"); const netEngine = @import("netEngine.zig"); pub const NetEngine = netEngine.NetEngine; pub const err = netEngine.net_err; @@ -8,6 +9,10 @@ pub const errs = netEngine.net_errs; pub const log = netEngine.net_log; pub const logs = netEngine.net_logs; +pub fn netAllocator() std.mem.Allocator { + return core.get(NetEngine).arenaAllocator(); +} + pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void { _ = args; _ = spec; @@ -18,7 +23,7 @@ pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.me } const engine = try core.createObject(NetEngine, .{ .can_tick = true }); - try engine.initalizeTransport(netEngine.EnetTransport); + _ = try engine.initalizeTransport(netEngine.EnetTransport); } pub fn shutdown_module(allocator: std.mem.Allocator) void { @@ -33,16 +38,6 @@ pub const Module = core.ModuleDescription{ .enabledByDefault = false, }; -pub const PeerType = enum { - offline, - server, - client, -}; - -pub const TransportType = enum { - enet, -}; - pub const TransportError = error{ UnknownStatePanic, BadInit, @@ -51,13 +46,6 @@ pub const TransportError = error{ 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, @@ -67,48 +55,54 @@ pub fn ip2String(allocator: std.mem.Allocator, ip: u32) ![:0]u8 { }); } +pub const TransportRef = netEngine.TransportRef; + pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", struct { - startServer: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!void, - stopServer: *const fn (*anyopaque) void, + hostSession: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session, + endSession: *const fn (*anyopaque, *Session) void, - startClient: *const fn (*anyopaque, target: []const u8) TransportError!void, + connect: *const fn (*anyopaque, target: []const u8) TransportError!*Link, + endLink: *const fn (*anyopaque, *Link) void, - connect: *const fn (*anyopaque, target: []const u8) TransportError!void, - sendMessage: *const fn (*anyopaque, connectionId: u32, data: []const u8, reliable: bool) TransportError!void, + sendMessage: *const fn (*anyopaque, *Link, []const u8, bool) TransportError!void, - deinitialize: *const fn (*anyopaque) 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 { + + // sessions interface + pub fn hostSession(p: *anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session { const ptr: *T = @ptrCast(@alignCast(p)); - ptr.startServer(bindInfo) catch return TransportError.ServerStartFailed; + return ptr.hostSession(bindInfo) catch return TransportError.ServerStartFailed; } - pub fn stopServer(p: *anyopaque) void { + pub fn endSession(p: *anyopaque, session: *Session) void { const ptr: *T = @ptrCast(@alignCast(p)); - ptr.stopServer(); + ptr.endSession(session); } - pub fn startClient(p: *anyopaque, target: []const u8) TransportError!void { + // links interface + pub fn connect(p: *anyopaque, target: []const u8) TransportError!*Link { const ptr: *T = @ptrCast(@alignCast(p)); - ptr.startClient(target) catch return TransportError.UnknownStatePanic; + return ptr.connect(target) catch return TransportError.UnknownStatePanic; } - pub fn connect(p: *anyopaque, target: []const u8) TransportError!void { + pub fn endLink(p: *anyopaque, link: *Link) void { const ptr: *T = @ptrCast(@alignCast(p)); - ptr.connect(target) catch return TransportError.UnknownStatePanic; + ptr.endLink(link); } - pub fn sendMessage(p: *anyopaque, connectionId: u32, data: []const u8, reliable: bool) TransportError!void { + pub fn sendMessageLink(p: *anyopaque, link: *Link, data: []const u8, reliable: bool) TransportError!void { const ptr: *T = @ptrCast(@alignCast(p)); - ptr.sendMessage(connectionId, data, reliable) catch return TransportError.UnknownStatePanic; + ptr.sendMessageLink(link, data, reliable) catch return TransportError.UnknownStatePanic; } - pub fn deinitialize(p: *anyopaque) void { - const ptr: *T = @ptrCast(@alignCast(p)); - ptr.deinitialize(); - } + // transport management + // pub fn deinitialize(p: *anyopaque) void { + // const ptr: *T = @ptrCast(@alignCast(p)); + // ptr.deinitialize(); + // } }; inline for (@typeInfo(Wrap).@"struct".decls) |d| { @@ -118,12 +112,194 @@ pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", st } return .{ - .startServer = Wrap.startServer, - .stopServer = Wrap.stopServer, - .startClient = Wrap.startClient, + .hostSession = Wrap.hostSession, + .endSession = Wrap.endSession, .connect = Wrap.connect, - .sendMessage = Wrap.sendMessage, - .deinitialize = Wrap.deinitialize, + .endLink = Wrap.endLink, + .sendMessage = Wrap.sendMessageLink, }; } }); + +const NewLinkCallback = struct { + data: *anyopaque, + func: Function, + + pub const Function = *const fn (*anyopaque, *Link) void; +}; + +pub const Session = struct { + transportName: []const u8 = "Dummy", + transport: ?TransportRef = null, + transportData: ?*anyopaque = null, + allocator: std.mem.Allocator, + skipEndSession: bool = false, + + newLinkCallback: std.ArrayListUnmanaged(NewLinkCallback) = .{}, + links: std.ArrayListUnmanaged(*Link) = .{}, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ + .allocator = allocator, + }; + return self; + } + + pub fn registerNewLinkCallback(self: *@This(), data: ?*anyopaque, callback: NewLinkCallback) void { + try self.newLinkCallback.append(.{ .data = data, .func = callback }); + } + + pub fn addLink(self: *@This(), link: *Link) !void { + try self.links.append(netAllocator(), link); + link.session = self; + + for (self.newLinkCallback.items) |cb| { + cb.func(cb.data, link); + } + } + + pub fn removeLink(self: *@This(), link: *Link) void { + for (self.links.items, 0..) |l, i| { + if (l == link) { + _ = self.links.swapRemove(i); + return; + } + } + } + + pub fn destroy(self: *@This()) void { + for (self.links.items) |link| { + link.destroy(); + } + + if (self.transport) |transport| { + if (!self.skipEndSession) { + transport.vtable.endSession(transport.ptr, self); + } + } + + self.newLinkCallback.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +// end result: +// Links and sessions should be very low level implementations +// +// for the gameplay framework: +// NetEngine will own the link and sessions, and all gameplay will speak to NetEngine. + +// host side flow +// +// initialize transport -> TransportInterface +// host Session -> Session // called by NetEngine +// // session now listens for incomming connections +// +// Transport Implementation calls Session->NewLink(); +// onLinkCreated -> Link +// +// Link.sendMessage(bytes []const u8, reliable:bool); // can send messages // if you decide to listen to link, you will recieve [[all]] data +// Link.poll() -> ?[]const u8 // recieve messages from the link? (to be owned by netengine) +// +// client side flow +// +// createLink(hostIp) -> Link +// +// + +pub const LinkState = enum { + invalid, + connecting, // first state when the peer connects, + connected, // +}; + +pub const LinkType = enum { + invalid, // + server, // + client, // + peer, +}; + +pub const LinkEventType = enum { + connected, + disconnected, + kicked, +}; + +pub const LinkEvent = struct { + link: *Link, + event: LinkEventType, +}; + +pub const LinkEventCallbackFn = *const fn (?*anyopaque, LinkEvent) void; + +pub const LinkEventDelegate = struct { + func: LinkEventCallbackFn, + ptr: ?*anyopaque, +}; + +pub const Link = struct { + allocator: std.mem.Allocator, + session: ?*Session = null, + + idString: []const u8 = "Uninitialized", + state: LinkState = .invalid, + linkType: LinkType = .invalid, + callbacks: std.ArrayListUnmanaged(LinkEventDelegate) = .{}, + + id: ?u32 = null, + transportData: ?*anyopaque = null, + transport: ?TransportRef = null, + + // closes, and pointer is invalidated + pub fn destroy(self: *@This()) void { + if (self.transport) |t| { + t.vtable.endLink(t.ptr, self); + } + + if (self.session) |session| { + session.removeLink(self); + } + + self.callbacks.deinit(self.allocator); + self.allocator.destroy(self); + } + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(*@This()); + + self.* = .{ + .allocator = allocator, + }; + + return self; + } + + pub fn sendMessage(self: *@This(), bytes: []const u8, reliable: bool) !void { + if (self.transport) |t| { + try t.vtable.sendMessage(t.ptr, self, bytes, reliable); + } + } + + pub fn registerLinkEventCallback(self: *@This(), data: ?*anyopaque, cb: LinkEventCallbackFn) !void { + try self.callbacks.append(.{ + .ptr = data, + .cb = cb, + }); + } +}; + +pub fn hostSession(bindInfo: ?[]const []const u8) !*Session { + if (core.get(NetEngine).transport) |t| { + return try t.vtable.hostSession(t.ptr, bindInfo); + } + return error.NoTransportAvailable; +} + +pub fn connect(target: []const u8) !*Link { + if (core.get(NetEngine).transport) |t| { + return try t.vtable.connect(t.ptr, target); + } + return error.NoTransportAvailable; +} diff --git a/engine/net/src/netEngine.zig b/engine/net/src/netEngine.zig index 32cc03f..8ddf2f7 100644 --- a/engine/net/src/netEngine.zig +++ b/engine/net/src/netEngine.zig @@ -19,35 +19,41 @@ pub fn net_errs(comptime fmt: []const u8) void { core.printInner("[NET ]: ERROR!! " ++ fmt ++ "\n", .{}); } +pub const TransportRef = core.Reference(net.TransportInterface); + pub const NetEngine = struct { pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.NetEngine"); allocator: std.mem.Allocator, - transport: ?core.Reference(net.TransportInterface) = null, + arena: std.heap.ArenaAllocator, + transport: ?TransportRef = null, pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); + self.* = @This(){ .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), }; return self; } - pub fn initalizeTransport(self: *@This(), comptime T: type) !void { + pub fn arenaAllocator(self: *@This()) std.mem.Allocator { + return self.arena.allocator(); + } + + pub fn initalizeTransport(self: *@This(), comptime T: type) !TransportRef { const transport = try core.createObject(T, .{ .can_tick = true }); - self.transport = core.Reference(net.TransportInterface){ + const interface = TransportRef{ .ptr = transport, .vtable = T.TransportInterfaceVTable, }; - net.log("transport initialized {s}", .{@typeName(T)}); - } + self.transport = interface; - pub fn startServer(self: *@This(), bindInfo: ?[]const []const u8) !void { - if (self.transport) |transport| { - try transport.vtable.startServer(transport.ptr, bindInfo); - } + net.log("transport initialized {s}", .{@typeName(T)}); + return interface; } pub fn shutdown(self: *@This()) void { @@ -56,6 +62,7 @@ pub const NetEngine = struct { } pub fn deinit(self: *@This()) void { + self.arena.deinit(); self.allocator.destroy(self); } @@ -65,3 +72,12 @@ pub const NetEngine = struct { // Network tick logic will go here } }; + +// responsibilities +// +// NetEngine +// - all gameobjects and high level gameplay talks to this one, and queues up messages +// +// TransportInterface +// - manages connection - startServer, stopServer, startClient, stopClient +// - diff --git a/projects/sampleGame/externGame/externGame.zig b/projects/sampleGame/externGame/externGame.zig index d9ef09e..7dfacc6 100644 --- a/projects/sampleGame/externGame/externGame.zig +++ b/projects/sampleGame/externGame/externGame.zig @@ -102,6 +102,9 @@ pub const ExternGameObject = struct { fireTraceFilter: physics.IgnoreFixedBodiesFilter = .{}, particleDebugger: extras.ParticleDebugger.ParticleDebugger = undefined, + session: ?*net.Session = null, + clientLink: ?*net.Link = null, + volume: f32 = 100, //lmao: bool = false, //lmao2: bool = true, @@ -390,7 +393,17 @@ pub const ExternGameObject = struct { } if (ig.smallButton("host")) { - core.get(net.NetEngine).startServer(&.{"7777"}) catch unreachable; + self.session = net.hostSession(&.{"7777"}) catch unreachable; + } + + if (ig.smallButton("connect")) { + self.clientLink = net.connect("127.0.0.1:7777") catch unreachable; + } + + if (self.clientLink) |link| { + if (ig.smallButton("sendMessageToServer")) { + link.sendMessage("hello from client", true) catch {}; + } } } ig.end(); @@ -409,6 +422,14 @@ pub const ExternGameObject = struct { } pub fn destroy(self: *@This()) void { + if (self.clientLink) |link| { + link.destroy(); + } + + if (self.session) |session| { + session.destroy(); + } + if (self.tbMap) |map| { map.destroy(); }