added loop back echoing between the server and client
This commit is contained in:
parent
f012815d93
commit
76fd816c2c
|
|
@ -453,3 +453,8 @@ pub fn SlackStruct(comptime T: type, comptime SlackSize: usize) type {
|
||||||
pub fn cast(comptime T: type, p: *anyopaque) T {
|
pub fn cast(comptime T: type, p: *anyopaque) T {
|
||||||
return @alignCast(@ptrCast(p));
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,113 @@ pub var TransportInterfaceVTable = net.TransportInterface.Implement(@This());
|
||||||
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.ENetTransport");
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.ENetTransport");
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
peerType: net.PeerType = .offline,
|
sessions: std.ArrayListUnmanaged(*net.Session) = .{},
|
||||||
|
deadSessions: std.ArrayListUnmanaged(*net.Session) = .{},
|
||||||
address: ?enet.ENetAddress = null,
|
|
||||||
host: ?*enet.ENetHost = null,
|
|
||||||
|
|
||||||
clients: std.ArrayListUnmanaged(net.ClientInfo) = .{},
|
|
||||||
|
|
||||||
const MAX_CLIENTS = 128;
|
const MAX_CLIENTS = 128;
|
||||||
const DEFAULT_PORT = 7777;
|
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() {
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
const self = try allocator.create(@This());
|
const self = try allocator.create(@This());
|
||||||
|
|
@ -24,106 +122,216 @@ pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stopServer(self: *@This()) void {
|
fn createSession(self: *@This(), address: ?enet.ENetAddress) !*net.Session {
|
||||||
// other shit
|
const session = try net.netAllocator().create(net.Session);
|
||||||
self.peerType = .offline;
|
session.* = .{
|
||||||
}
|
.transportName = "ENet",
|
||||||
|
.transport = .{ .vtable = TransportInterfaceVTable, .ptr = self },
|
||||||
pub fn startClient(self: *@This(), target: []const u8) !void {
|
.allocator = net.netAllocator(),
|
||||||
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;
|
const transportData = try net.netAllocator().create(ENetSessionData);
|
||||||
|
transportData.session = session;
|
||||||
|
|
||||||
// Connect to target will be handled separately
|
if (address != null) {
|
||||||
_ = target;
|
transportData.address = address.?;
|
||||||
}
|
transportData.host = enet.enet_host_create(&transportData.address, MAX_CLIENTS, 2, 0, 0) orelse {
|
||||||
|
|
||||||
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;
|
return error.ServerStartFailed;
|
||||||
};
|
};
|
||||||
|
net.log("host created on port {d}", .{address.?.port});
|
||||||
net.log("server started hosting on port {d}", .{port});
|
} else {
|
||||||
|
transportData.host = enet.enet_host_create(null, 1, 2, 0, 0) orelse {
|
||||||
self.peerType = .server;
|
return error.ClientStartFailed;
|
||||||
|
};
|
||||||
|
transportData.address = .{
|
||||||
|
.host = 0,
|
||||||
|
.port = 0,
|
||||||
|
};
|
||||||
|
net.log("client session created", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect(self: *@This(), target: []const u8) !void {
|
session.transportData = transportData;
|
||||||
var i: isize = @as(isize, @intCast(target.len)) - 1;
|
try self.sessions.append(self.allocator, session);
|
||||||
var port: u16 = DEFAULT_PORT;
|
|
||||||
|
|
||||||
var targetStr: ?[]const u8 = target;
|
return session;
|
||||||
var portString: ?[]const u8 = null;
|
}
|
||||||
|
|
||||||
while (i > 0) : (i -= 1) {
|
pub fn hostSession(self: *@This(), bindInfo: ?[]const []const u8) !*net.Session {
|
||||||
if (target[@as(usize, @intCast(i))] == ':') {
|
const port: u16 = try std.fmt.parseInt(u16, bindInfo.?[0], 0);
|
||||||
portString = target[@as(usize, @intCast(i)) + 1 .. target.len];
|
const session = try self.createSession(enet.ENetAddress{
|
||||||
targetStr = target[0..@as(usize, @intCast(i))];
|
.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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (portString) |ps| {
|
var p: u16 = DEFAULT_PORT;
|
||||||
port = try std.fmt.parseInt(u16, ps, 0);
|
|
||||||
|
if (portStr) |ps| {
|
||||||
|
p = try std.fmt.parseInt(u16, ps, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf: [256]u8 = undefined;
|
return .{ .port = p, .address = try std.fmt.allocPrintZ(allocator, "{s}", .{base}) };
|
||||||
const targetName = try std.fmt.bufPrintZ(&buf, "{s}", .{targetStr.?});
|
}
|
||||||
|
|
||||||
self.address = enet.ENetAddress{
|
pub fn createLink(self: *@This(), session: *net.Session) !*net.Link {
|
||||||
.host = 0,
|
const newLink = try net.netAllocator().create(net.Link);
|
||||||
.port = port,
|
|
||||||
|
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 {
|
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
|
||||||
|
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);
|
||||||
|
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;
|
_ = self;
|
||||||
_ = connectionId;
|
|
||||||
_ = data;
|
|
||||||
_ = reliable;
|
|
||||||
// TODO: Implement message sending
|
|
||||||
return error.UnknownStatePanic;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinitialize(self: *@This()) void {
|
const linkData = getLinkData(link);
|
||||||
if (self.host) |host| {
|
linkData.queuePacketData(data, reliable);
|
||||||
enet.enet_host_destroy(host);
|
|
||||||
self.host = null;
|
|
||||||
}
|
|
||||||
self.clients.deinit(self.allocator);
|
|
||||||
enet_mod.deinitialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preTick(self: *@This(), dt: f64) !void {
|
pub fn preTick(self: *@This(), dt: f64) !void {
|
||||||
switch (self.peerType) {
|
//switch (self.peerType) {
|
||||||
.server => {
|
// .server => {
|
||||||
self.tickServer(dt);
|
// self.tickServer(dt);
|
||||||
},
|
// },
|
||||||
.client => {
|
// .client => {
|
||||||
self.tickClient(dt);
|
// self.tickClient(dt);
|
||||||
},
|
// },
|
||||||
else => {},
|
//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) {
|
if (result > 0) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
enet.ENET_EVENT_TYPE_CONNECT => {
|
enet.ENET_EVENT_TYPE_CONNECT => {
|
||||||
// should add to clientsWaiting,
|
core.engine_log("client connected!!!!");
|
||||||
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 => {
|
enet.ENET_EVENT_TYPE_RECEIVE => {
|
||||||
const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength];
|
const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength];
|
||||||
_ = data;
|
_ = 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);
|
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 => {
|
enet.ENET_EVENT_TYPE_DISCONNECT => {
|
||||||
|
|
@ -180,7 +359,6 @@ pub fn tickServer(self: *@This(), dt: f64) void {
|
||||||
_ = self.clients.swapRemove(i);
|
_ = self.clients.swapRemove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// event.peer.*.data = null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
|
|
@ -196,9 +374,12 @@ pub fn tickClient(self: *@This(), dt: f64) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *@This()) void {
|
pub fn destroy(self: *@This()) void {
|
||||||
if (self.host) |host| {
|
for (self.sessions.items) |session| {
|
||||||
enet.enet_host_destroy(host);
|
session.destroy();
|
||||||
}
|
}
|
||||||
|
self.sessions.deinit(self.allocator);
|
||||||
|
self.deadSessions.deinit(self.allocator);
|
||||||
|
|
||||||
enet_mod.deinitialize();
|
enet_mod.deinitialize();
|
||||||
self.allocator.destroy(self);
|
self.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const core = @import("core");
|
const core = @import("core");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const net = @import("net.zig");
|
||||||
const netEngine = @import("netEngine.zig");
|
const netEngine = @import("netEngine.zig");
|
||||||
pub const NetEngine = netEngine.NetEngine;
|
pub const NetEngine = netEngine.NetEngine;
|
||||||
pub const err = netEngine.net_err;
|
pub const err = netEngine.net_err;
|
||||||
|
|
@ -8,6 +9,10 @@ pub const errs = netEngine.net_errs;
|
||||||
pub const log = netEngine.net_log;
|
pub const log = netEngine.net_log;
|
||||||
pub const logs = netEngine.net_logs;
|
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 {
|
pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void {
|
||||||
_ = args;
|
_ = args;
|
||||||
_ = spec;
|
_ = 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 });
|
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 {
|
pub fn shutdown_module(allocator: std.mem.Allocator) void {
|
||||||
|
|
@ -33,16 +38,6 @@ pub const Module = core.ModuleDescription{
|
||||||
.enabledByDefault = false,
|
.enabledByDefault = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PeerType = enum {
|
|
||||||
offline,
|
|
||||||
server,
|
|
||||||
client,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TransportType = enum {
|
|
||||||
enet,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TransportError = error{
|
pub const TransportError = error{
|
||||||
UnknownStatePanic,
|
UnknownStatePanic,
|
||||||
BadInit,
|
BadInit,
|
||||||
|
|
@ -51,13 +46,6 @@ pub const TransportError = error{
|
||||||
ServerStartFailed,
|
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 {
|
pub fn ip2String(allocator: std.mem.Allocator, ip: u32) ![:0]u8 {
|
||||||
return try std.fmt.allocPrintZ(allocator, "{d}.{d}.{d}.{d}", .{
|
return try std.fmt.allocPrintZ(allocator, "{d}.{d}.{d}.{d}", .{
|
||||||
(ip >> 0) & 0xFF,
|
(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 {
|
pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", struct {
|
||||||
startServer: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!void,
|
hostSession: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session,
|
||||||
stopServer: *const fn (*anyopaque) void,
|
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, *Link, []const u8, bool) TransportError!void,
|
||||||
sendMessage: *const fn (*anyopaque, connectionId: u32, data: []const u8, reliable: bool) TransportError!void,
|
|
||||||
|
|
||||||
deinitialize: *const fn (*anyopaque) void,
|
// deinitialize: *const fn (*anyopaque) void,
|
||||||
|
|
||||||
pub fn Implement(comptime T: type) @This() {
|
pub fn Implement(comptime T: type) @This() {
|
||||||
const Wrap = struct {
|
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));
|
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));
|
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));
|
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));
|
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));
|
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 {
|
// transport management
|
||||||
const ptr: *T = @ptrCast(@alignCast(p));
|
// pub fn deinitialize(p: *anyopaque) void {
|
||||||
ptr.deinitialize();
|
// const ptr: *T = @ptrCast(@alignCast(p));
|
||||||
}
|
// ptr.deinitialize();
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (@typeInfo(Wrap).@"struct".decls) |d| {
|
inline for (@typeInfo(Wrap).@"struct".decls) |d| {
|
||||||
|
|
@ -118,12 +112,194 @@ pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", st
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.startServer = Wrap.startServer,
|
.hostSession = Wrap.hostSession,
|
||||||
.stopServer = Wrap.stopServer,
|
.endSession = Wrap.endSession,
|
||||||
.startClient = Wrap.startClient,
|
|
||||||
.connect = Wrap.connect,
|
.connect = Wrap.connect,
|
||||||
.sendMessage = Wrap.sendMessage,
|
.endLink = Wrap.endLink,
|
||||||
.deinitialize = Wrap.deinitialize,
|
.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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,35 +19,41 @@ pub fn net_errs(comptime fmt: []const u8) void {
|
||||||
core.printInner("[NET ]: ERROR!! " ++ fmt ++ "\n", .{});
|
core.printInner("[NET ]: ERROR!! " ++ fmt ++ "\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const TransportRef = core.Reference(net.TransportInterface);
|
||||||
|
|
||||||
pub const NetEngine = struct {
|
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,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
transport: ?TransportRef = 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());
|
||||||
|
|
||||||
self.* = @This(){
|
self.* = @This(){
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
return self;
|
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 });
|
const transport = try core.createObject(T, .{ .can_tick = true });
|
||||||
self.transport = core.Reference(net.TransportInterface){
|
const interface = TransportRef{
|
||||||
.ptr = transport,
|
.ptr = transport,
|
||||||
.vtable = T.TransportInterfaceVTable,
|
.vtable = T.TransportInterfaceVTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
net.log("transport initialized {s}", .{@typeName(T)});
|
self.transport = interface;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn startServer(self: *@This(), bindInfo: ?[]const []const u8) !void {
|
net.log("transport initialized {s}", .{@typeName(T)});
|
||||||
if (self.transport) |transport| {
|
return interface;
|
||||||
try transport.vtable.startServer(transport.ptr, bindInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(self: *@This()) void {
|
pub fn shutdown(self: *@This()) void {
|
||||||
|
|
@ -56,6 +62,7 @@ pub const NetEngine = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.arena.deinit();
|
||||||
self.allocator.destroy(self);
|
self.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,3 +72,12 @@ pub const NetEngine = struct {
|
||||||
// Network tick logic will go here
|
// 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
|
||||||
|
// -
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,9 @@ pub const ExternGameObject = struct {
|
||||||
fireTraceFilter: physics.IgnoreFixedBodiesFilter = .{},
|
fireTraceFilter: physics.IgnoreFixedBodiesFilter = .{},
|
||||||
particleDebugger: extras.ParticleDebugger.ParticleDebugger = undefined,
|
particleDebugger: extras.ParticleDebugger.ParticleDebugger = undefined,
|
||||||
|
|
||||||
|
session: ?*net.Session = null,
|
||||||
|
clientLink: ?*net.Link = null,
|
||||||
|
|
||||||
volume: f32 = 100,
|
volume: f32 = 100,
|
||||||
//lmao: bool = false,
|
//lmao: bool = false,
|
||||||
//lmao2: bool = true,
|
//lmao2: bool = true,
|
||||||
|
|
@ -390,7 +393,17 @@ pub const ExternGameObject = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ig.smallButton("host")) {
|
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();
|
ig.end();
|
||||||
|
|
@ -409,6 +422,14 @@ pub const ExternGameObject = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *@This()) void {
|
pub fn destroy(self: *@This()) void {
|
||||||
|
if (self.clientLink) |link| {
|
||||||
|
link.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.session) |session| {
|
||||||
|
session.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (self.tbMap) |map| {
|
if (self.tbMap) |map| {
|
||||||
map.destroy();
|
map.destroy();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue