410 lines
13 KiB
Zig
410 lines
13 KiB
Zig
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;
|