const core = @import("core"); const std = @import("std"); const net = @import("net.zig"); const netEngine = @import("netEngine.zig"); pub const EnetTransport = netEngine.EnetTransport; pub const NetEngine = netEngine.NetEngine; pub const err = netEngine.net_err; 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; _ = allocator; if (core.isUtility()) { return; } const engine = try core.createObject(NetEngine, .{ .can_tick = true }); _ = try engine.initalizeTransport(netEngine.EnetTransport); } pub fn shutdown_module(allocator: std.mem.Allocator) void { core.get(NetEngine).shutdown(); _ = allocator; } pub const context = core.EngineObject(NetEngine).get; pub const Module = core.ModuleDescription{ .name = "net", .enabledByDefault = false, }; pub const TransportError = error{ UnknownStatePanic, BadInit, UnknownError, OutOfMemory, ServerStartFailed, }; pub fn ip2String(allocator: std.mem.Allocator, ip: u32) ![:0]u8 { return try std.fmt.allocPrintSentinel(allocator, "{d}.{d}.{d}.{d}", .{ (ip >> 0) & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF, }, 0); } pub const TransportRef = netEngine.TransportRef; pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", struct { hostSession: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session, endSession: *const fn (*anyopaque, *Session) void, connect: *const fn (*anyopaque, target: []const u8) TransportError!*Link, endLink: *const fn (*anyopaque, *Link) void, sendMessage: *const fn (*anyopaque, *Link, []const u8, bool) TransportError!void, // deinitialize: *const fn (*anyopaque) void, pub fn Implement(comptime T: type) @This() { const Wrap = struct { // sessions interface pub fn hostSession(p: *anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session { const ptr: *T = @ptrCast(@alignCast(p)); return ptr.hostSession(bindInfo) catch return TransportError.ServerStartFailed; } pub fn endSession(p: *anyopaque, session: *Session) void { const ptr: *T = @ptrCast(@alignCast(p)); ptr.endSession(session); } // links interface pub fn connect(p: *anyopaque, target: []const u8) TransportError!*Link { const ptr: *T = @ptrCast(@alignCast(p)); return ptr.connect(target) catch return TransportError.UnknownStatePanic; } pub fn endLink(p: *anyopaque, link: *Link) void { const ptr: *T = @ptrCast(@alignCast(p)); ptr.endLink(link); } pub fn sendMessageLink(p: *anyopaque, link: *Link, data: []const u8, reliable: bool) TransportError!void { const ptr: *T = @ptrCast(@alignCast(p)); ptr.sendMessageLink(link, data, reliable) catch return TransportError.UnknownStatePanic; } // transport management // 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 .{ .hostSession = Wrap.hostSession, .endSession = Wrap.endSession, .connect = Wrap.connect, .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; }