pub var TransportInterfaceVTable = net.TransportInterface.Implement(@This()); pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.ENetTransport"); allocator: std.mem.Allocator = undefined, 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), testLinkPosition: core.Vectorf = .{}, 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; } pub fn readDebugVector(self: *@This(), data: []const u8) void { self.testLinkPosition = @as(*const core.Vectorf, @ptrCast(@alignCast(data.ptr))).*; //core.engine_log("reading vector position: {d} {d} {d}", self.testLinkPosition); } pub fn pushDebugLoc(self: *@This()) void { // const x = self.testLinkPosition; // core.engine_log("sending vector position: {d} {d} {d}", x); const allocator = net.netAllocator(); self.queuedMessages.push(.{ .bytes = allocator.dupe(u8, &@as([@sizeOf(core.Vectorf)]u8, @bitCast(self.testLinkPosition))) catch unreachable, .reliable = true, }) catch unreachable; } pub fn sendPacketChannelDebug(self: *@This(), bytes: []const u8, reliable: bool, channel: u8) void { if (enet.enet_packet_create(bytes.ptr, bytes.len, if (reliable) enet.ENET_PACKET_FLAG_RELIABLE else 0)) |packet| { _ = enet.enet_peer_send(self.peer, channel, packet); } } // 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 0)) |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, debugTick: f64 = 0.05, 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(), dt: f64) 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 = "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); link.linkType = .server; }, 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 #{d} channel {d}: '{s}' echoing it back", .{ self.messageCount, event.channelID, data }); if (self.findLinkByPeer(event.peer)) |link| { const linkData = getLinkData(link); if (link.linkType == .server) { // linkData.queuePacketData(data, true); if (std.mem.startsWith(u8, data, "cname:")) { const ipAsString = net.ip2String(net.netAllocator(), event.peer.*.address.host) catch unreachable; const id = std.fmt.allocPrint(net.netAllocator(), "{s}@{s}", .{ data[6..data.len], ipAsString }) catch unreachable; link.idString = id; } } if (link.linkType == .client) { if (!std.mem.startsWith(u8, data, "msg:")) { linkData.readDebugVector(data); } } } // Destroy the received packet enet.enet_packet_destroy(event.packet); }, else => {}, } } for (self.session.links.items) |link| { const linkData = getLinkData(link); self.debugTick -= dt; if (self.debugTick < 0) { self.debugTick = 0.05; if (link.linkType == .server) { linkData.pushDebugLoc(); } } while (linkData.queuedMessages.pop()) |queuedData| { linkData.sendPacket(queuedData.bytes, queuedData.reliable); net.netAllocator().free(queuedData.bytes); } } enet.enet_host_flush(self.host); } }; pub fn create(self: *@This(), allocator: std.mem.Allocator, first: bool) !*@This() { if (!first) return; self.* = .{ .allocator = allocator, }; try enet_mod.initialize(); return self; } 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(), }; const transportData = try net.netAllocator().create(ENetSessionData); transportData.session = session; 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 hostSession(self: *@This(), bindInfo: ?[]const []const u8) !*net.Session { const port: u16 = try std.fmt.parseInt(u16, bindInfo.?[0], 0); const session = try self.createSession(enet.ENetAddress{ .host = enet.ENET_HOST_ANY, .port = port, }); return session; } 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); } for (self.sessions.items, 0..) |s, i| { if (s == session) { _ = self.sessions.orderedRemove(i); return; } } } 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; } } var p: u16 = DEFAULT_PORT; if (portStr) |ps| { p = try std.fmt.parseInt(u16, ps, 10); } return .{ .port = p, .address = try std.fmt.allocPrintSentinel(allocator, "{s}", .{base}, 0) }; } 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), }; newLink.transportData = linkInfo; newLink.session = session; try session.addLink(newLink); return newLink; } pub fn getSessionData(session: *net.Session) *ENetSessionData { return core.cast(*ENetSessionData, session.transportData.?); } pub fn getLinkData(link: *net.Link) *ENetLinkData { return core.cast(*ENetLinkData, link.transportData.?); } // creates a session and a link with that session, to the host 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(); } const newLink = try self.createLink(session); newLink.linkType = .client; 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.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 => {}, // } for (self.sessions.items) |session| { const sessionInfo = getSessionData(session); sessionInfo.tick(dt); // tick messages in here } } //pub fn tickServer(self: *@This(), dt: f64) void { // } pub fn tickClient(self: *@This(), dt: f64) void { _ = self; _ = dt; } pub fn destroy(self: *@This()) void { for (self.sessions.items) |session| { session.destroy(); } self.sessions.deinit(self.allocator); self.deadSessions.deinit(self.allocator); enet_mod.deinitialize(); } const core = @import("core"); const net = @import("../net.zig"); const std = @import("std"); const enet_mod = @import("enet"); const enet = enet_mod.c;