mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00
Start working on the server:
It can now do a handshake (sort of). I also added a CommandQueue for the gui system to prevent issues from closing windows while they are being updated.
This commit is contained in:
parent
f1681d1687
commit
b98f565f2e
@ -45,6 +45,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
}
|
||||
exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c", "lib/stb_image_write.c"}, &[_][]const u8{"-g", "-O3"});
|
||||
exe.addAnonymousModule("gui", .{.source_file = .{.path = "src/gui/gui.zig"}});
|
||||
exe.addAnonymousModule("server", .{.source_file = .{.path = "src/server/server.zig"}});
|
||||
const harfbuzzModule = freetype.harfbuzzModule(b);
|
||||
const freetypeModule = harfbuzzModule.dependencies.get("freetype").?;
|
||||
exe.addModule("harfbuzz", harfbuzzModule);
|
||||
|
@ -155,7 +155,9 @@ pub fn mainButtonReleased(self: *GuiWindow, mousePosition: Vec2f) void {
|
||||
if(mousePosition[0] - self.pos[0] > self.size[0] - 32*self.scale) {
|
||||
if(mousePosition[0] - self.pos[0] > self.size[0] - 16*self.scale) {
|
||||
// Close
|
||||
gui.closeWindow(self);
|
||||
gui.closeWindow(self) catch |err| {
|
||||
std.log.err("Got error while trying to close a window: {s}", .{@errorName(err)});
|
||||
};
|
||||
return;
|
||||
} else {
|
||||
// Zoom out
|
||||
|
111
src/gui/gui.zig
111
src/gui/gui.zig
@ -34,6 +34,85 @@ pub var scale: f32 = undefined;
|
||||
pub var hoveredItemSlot: ?*ItemSlot = null;
|
||||
pub var hoveredCraftingSlot: ?*CraftingResultSlot = null;
|
||||
|
||||
const GuiCommandQueue = struct {
|
||||
const Action = enum {
|
||||
open,
|
||||
close,
|
||||
};
|
||||
const Command = struct {
|
||||
window: *GuiWindow,
|
||||
action: Action,
|
||||
};
|
||||
|
||||
var commands: std.ArrayList(Command) = undefined;
|
||||
var mutex: std.Thread.Mutex = .{};
|
||||
|
||||
fn init() void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
commands = std.ArrayList(Command).init(main.globalAllocator);
|
||||
}
|
||||
|
||||
fn deinit() void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
commands.deinit();
|
||||
}
|
||||
|
||||
fn scheduleCommand(command: Command) !void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
try commands.append(command);
|
||||
}
|
||||
|
||||
fn executeCommands() !void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
for(commands.items) |command| {
|
||||
switch(command.action) {
|
||||
.open => {
|
||||
try executeOpenWindowCommand(command.window);
|
||||
},
|
||||
.close => {
|
||||
executeCloseWindowCommand(command.window);
|
||||
}
|
||||
}
|
||||
}
|
||||
commands.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
fn executeOpenWindowCommand(window: *GuiWindow) !void {
|
||||
std.debug.assert(!mutex.tryLock()); // mutex must be locked.
|
||||
defer updateWindowPositions();
|
||||
for(openWindows.items, 0..) |_openWindow, i| {
|
||||
if(_openWindow == window) {
|
||||
_ = openWindows.swapRemove(i);
|
||||
openWindows.appendAssumeCapacity(window);
|
||||
selectedWindow = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try openWindows.append(window);
|
||||
try window.onOpenFn();
|
||||
selectedWindow = null;
|
||||
}
|
||||
|
||||
fn executeCloseWindowCommand(window: *GuiWindow) void {
|
||||
std.debug.assert(!mutex.tryLock()); // mutex must be locked.
|
||||
defer updateWindowPositions();
|
||||
if(selectedWindow == window) {
|
||||
selectedWindow = null;
|
||||
}
|
||||
for(openWindows.items, 0..) |_openWindow, i| {
|
||||
if(_openWindow == window) {
|
||||
_ = openWindows.swapRemove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.onCloseFn();
|
||||
}
|
||||
};
|
||||
|
||||
pub const Callback = struct {
|
||||
callback: ?*const fn(usize) void = null,
|
||||
arg: usize = 0,
|
||||
@ -46,6 +125,7 @@ pub const Callback = struct {
|
||||
};
|
||||
|
||||
pub fn init() !void {
|
||||
GuiCommandQueue.init();
|
||||
windowList = std.ArrayList(*GuiWindow).init(main.globalAllocator);
|
||||
hudWindows = std.ArrayList(*GuiWindow).init(main.globalAllocator);
|
||||
openWindows = std.ArrayList(*GuiWindow).init(main.globalAllocator);
|
||||
@ -104,6 +184,7 @@ pub fn deinit() void {
|
||||
}
|
||||
}
|
||||
inventory.deinit();
|
||||
GuiCommandQueue.deinit();
|
||||
}
|
||||
|
||||
fn save() !void {
|
||||
@ -241,18 +322,7 @@ pub fn openWindow(id: []const u8) Allocator.Error!void {
|
||||
}
|
||||
|
||||
pub fn openWindowFromRef(window: *GuiWindow) Allocator.Error!void {
|
||||
defer updateWindowPositions();
|
||||
for(openWindows.items, 0..) |_openWindow, i| {
|
||||
if(_openWindow == window) {
|
||||
_ = openWindows.swapRemove(i);
|
||||
openWindows.appendAssumeCapacity(window);
|
||||
selectedWindow = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try openWindows.append(window);
|
||||
try window.onOpenFn();
|
||||
selectedWindow = null;
|
||||
try GuiCommandQueue.scheduleCommand(.{.action = .open, .window = window});
|
||||
}
|
||||
|
||||
pub fn toggleWindow(id: []const u8) Allocator.Error!void {
|
||||
@ -295,18 +365,8 @@ pub fn openWindowCallback(comptime id: []const u8) Callback {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn closeWindow(window: *GuiWindow) void {
|
||||
defer updateWindowPositions();
|
||||
if(selectedWindow == window) {
|
||||
selectedWindow = null;
|
||||
}
|
||||
for(openWindows.items, 0..) |_openWindow, i| {
|
||||
if(_openWindow == window) {
|
||||
_ = openWindows.swapRemove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.onCloseFn();
|
||||
pub fn closeWindow(window: *GuiWindow) !void {
|
||||
try GuiCommandQueue.scheduleCommand(.{.action = .close, .window = window});
|
||||
}
|
||||
|
||||
pub fn setSelectedTextInput(newSelectedTextInput: ?*TextInput) void {
|
||||
@ -468,6 +528,7 @@ pub fn updateWindowPositions() void {
|
||||
pub fn updateAndRenderGui() !void {
|
||||
const mousePos = main.Window.getMousePosition()/@splat(2, scale);
|
||||
hoveredAWindow = false;
|
||||
try GuiCommandQueue.executeCommands();
|
||||
if(!main.Window.grabbed) {
|
||||
if(selectedWindow) |selected| {
|
||||
try selected.updateSelected(mousePos);
|
||||
@ -604,7 +665,7 @@ pub const inventory = struct {
|
||||
}
|
||||
|
||||
fn render(mousePos: Vec2f) !void {
|
||||
carriedItemSlot.pos = mousePos;
|
||||
carriedItemSlot.pos = mousePos - Vec2f{12, 12};
|
||||
try carriedItemSlot.render(.{0, 0});
|
||||
// Draw tooltip:
|
||||
if(carriedItemStack.amount == 0) if(hoveredItemSlot) |hovered| {
|
||||
|
@ -29,7 +29,10 @@ fn apply(_: usize) void {
|
||||
return;
|
||||
};
|
||||
|
||||
gui.closeWindow(&window);
|
||||
gui.closeWindow(&window) catch |err| {
|
||||
std.log.err("Encountered error in change_name.apply while closing window: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
if(oldName.len == 0) {
|
||||
gui.openWindow("main") catch |err| {
|
||||
std.log.err("Encountered error in change_name.apply: {s}", .{@errorName(err)});
|
||||
|
@ -71,7 +71,9 @@ fn join(_: usize) void {
|
||||
std.log.err("No connection found. Cannot connect.", .{});
|
||||
}
|
||||
for(gui.openWindows.items) |openWindow| {
|
||||
gui.closeWindow(openWindow);
|
||||
gui.closeWindow(openWindow) catch |err| {
|
||||
std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
gui.openHud() catch |err| {
|
||||
std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
|
||||
|
@ -28,8 +28,27 @@ fn openWorld(namePtr: usize) void {
|
||||
const nullTerminatedName = @intToPtr([*:0]const u8, namePtr);
|
||||
const name = std.mem.span(nullTerminatedName);
|
||||
std.log.info("TODO: Open world {s}", .{name});
|
||||
// new Thread(() -> Server.main(new String[] {name}), "Server Thread").start();
|
||||
// Cubyz.gameUI.setMenu(null, false); // hide from UISystem.back()
|
||||
_ = std.Thread.spawn(.{}, main.server.start, .{}) catch |err| {
|
||||
std.log.err("Encountered error while starting server thread: {s}", .{@errorName(err)});
|
||||
};
|
||||
|
||||
const connection = ConnectionManager.init(main.settings.defaultPort+1, false) catch |err| {
|
||||
std.log.err("Encountered error while opening connection: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
connection.world = &main.game.testWorld;
|
||||
main.game.testWorld.init("127.0.0.1", connection) catch |err| {
|
||||
std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
|
||||
};
|
||||
main.game.world = &main.game.testWorld;
|
||||
for(gui.openWindows.items) |openWindow| {
|
||||
gui.closeWindow(openWindow) catch |err| {
|
||||
std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
gui.openHud() catch |err| {
|
||||
std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
|
||||
};
|
||||
// while(Server.world == null) {
|
||||
// try {
|
||||
// Thread.sleep(10);
|
||||
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const gui = @import("gui");
|
||||
pub const server = @import("server");
|
||||
|
||||
pub const assets = @import("assets.zig");
|
||||
pub const blocks = @import("blocks.zig");
|
||||
|
@ -602,14 +602,15 @@ pub const Protocols = struct {
|
||||
defer arrayList.deinit();
|
||||
try arrayList.append(stepAssets);
|
||||
try utils.Compression.pack(dir, arrayList.writer());
|
||||
std.log.debug("{any}", .{arrayList.items});
|
||||
try conn.sendImportant(id, arrayList.items);
|
||||
try conn.flush();
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// JsonObject jsonObject = new JsonObject();
|
||||
// ((User)conn).initPlayer(name);
|
||||
conn.user.?.initPlayer(name);
|
||||
const jsonObject = try JsonElement.initObject(main.threadAllocator);
|
||||
defer jsonObject.free(main.threadAllocator);
|
||||
// TODO:
|
||||
// jsonObject.put("player", ((User)conn).player.save());
|
||||
// jsonObject.put("player_id", ((User)conn).player.id);
|
||||
// jsonObject.put("blockPalette", Server.world.blockPalette.save());
|
||||
@ -618,14 +619,12 @@ pub const Protocols = struct {
|
||||
// spawn.put("y", Server.world.spawn.y);
|
||||
// spawn.put("z", Server.world.spawn.z);
|
||||
// jsonObject.put("spawn", spawn);
|
||||
// byte[] string = jsonObject.toString().getBytes(StandardCharsets.UTF_8);
|
||||
// byte[] outData = new byte[string.length + 1];
|
||||
// outData[0] = STEP_SERVER_DATA;
|
||||
// System.arraycopy(string, 0, outData, 1, string.length);
|
||||
// state.put(conn, STEP_SERVER_DATA);
|
||||
// conn.sendImportant(this, outData);
|
||||
// state.remove(conn); // Handshake is done.
|
||||
// conn.handShakeComplete = true;
|
||||
const outData = try jsonObject.toStringEfficient(main.threadAllocator, &[1]u8{stepServerData});
|
||||
defer main.threadAllocator.free(outData);
|
||||
try conn.sendImportant(id, outData);
|
||||
conn.handShakeState = stepServerData;
|
||||
conn.handShakeState = stepComplete;
|
||||
// TODO:
|
||||
// synchronized(conn) { // Notify the waiting server thread.
|
||||
// conn.notifyAll();
|
||||
// }
|
||||
@ -1212,16 +1211,6 @@ pub const Protocols = struct {
|
||||
pub fn send(conn: *Connection, data: []const u8) !void {
|
||||
try conn.sendImportant(id, data);
|
||||
}
|
||||
// TODO
|
||||
// public void sendToClients(String msg) {
|
||||
// Logger.log("chat", msg, "\033[0;32m");
|
||||
// synchronized(this) {
|
||||
// for(User user : Server.users) {
|
||||
// send(user, msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
};
|
||||
};
|
||||
|
||||
@ -1236,6 +1225,7 @@ pub const Connection = struct {
|
||||
var packetsResent: u32 = 0;
|
||||
|
||||
manager: *ConnectionManager,
|
||||
user: ?*main.server.User = null,
|
||||
|
||||
gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined, // TODO: Removing this line causes a compiler crash. #15150
|
||||
|
||||
@ -1563,7 +1553,7 @@ pub const Connection = struct {
|
||||
if(Protocols.list[protocol]) |prot| {
|
||||
try prot(self, data);
|
||||
} else {
|
||||
std.log.warn("Received unknown important protocol width id {}", .{protocol});
|
||||
std.log.warn("Received unknown important protocol with id {}", .{protocol});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
228
src/server/server.zig
Normal file
228
src/server/server.zig
Normal file
@ -0,0 +1,228 @@
|
||||
const std = @import("std");
|
||||
|
||||
const main = @import("root");
|
||||
const network = main.network;
|
||||
const Connection = network.Connection;
|
||||
const ConnectionManager = network.ConnectionManager;
|
||||
const utils = main.utils;
|
||||
const vec = main.vec;
|
||||
const Vec3d = vec.Vec3d;
|
||||
|
||||
|
||||
pub const User = struct {
|
||||
conn: *Connection,
|
||||
//TODO: public Player player;
|
||||
timeDifference: utils.TimeDifference = .{},
|
||||
interpolation: utils.GenericInterpolation(3) = undefined,
|
||||
lastTime: i16 = undefined,
|
||||
name: []const u8 = "",
|
||||
renderDistance: u16 = undefined,
|
||||
lodFactor: f32 = undefined,
|
||||
receivedFirstEntityData: bool = false,
|
||||
pos: [3]f64 = undefined, // TODO: Use position from te entity.
|
||||
vel: [3]f64 = undefined,
|
||||
// TODO: ipPort: []const u8,
|
||||
// TODO: public Thread waitingThread;
|
||||
|
||||
pub fn init(manager: *ConnectionManager, ipPort: []const u8) !*User {
|
||||
const self = try main.globalAllocator.create(User);
|
||||
self.* = User {
|
||||
.conn = try Connection.init(manager, ipPort),
|
||||
};
|
||||
self.conn.user = self;
|
||||
self.interpolation.init(&self.pos, &self.vel);
|
||||
// TODO: self.interpolation.init(&player.pos, &player.vel);
|
||||
network.Protocols.handShake.serverSide(self.conn);
|
||||
// TODO:
|
||||
// synchronized(this) {
|
||||
// waitingThread = Thread.currentThread();
|
||||
// this.wait();
|
||||
// waitingThread = null;
|
||||
// }
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *User) void {
|
||||
main.globalAllocator.destroy(self);
|
||||
self.conn.deinit();
|
||||
}
|
||||
// @Override
|
||||
// public void disconnect() {
|
||||
// super.disconnect();
|
||||
// Server.disconnect(this);
|
||||
// }
|
||||
|
||||
pub fn initPlayer(self: *User, name: []const u8) void {
|
||||
self.name = name;
|
||||
// TODO:
|
||||
// assert(player == null);
|
||||
// player = Server.world.findPlayer(this);
|
||||
// interpolation.outPosition[0] = player.getPosition().x;
|
||||
// interpolation.outPosition[1] = player.getPosition().y;
|
||||
// interpolation.outPosition[2] = player.getPosition().z;
|
||||
// interpolation.outVelocity[0] = player.vx;
|
||||
// interpolation.outVelocity[1] = player.vy;
|
||||
// interpolation.outVelocity[2] = player.vz;
|
||||
}
|
||||
|
||||
pub fn update(self: *User) void {
|
||||
var time = @truncate(i16, std.time.milliTimestamp()) -% main.settings.entityLookback;
|
||||
time -= self.timeDifference.difference.load(.Monotonic);
|
||||
self.interpolation.update(time, self.lastTime);
|
||||
// TODO:
|
||||
// player.getPosition().x = interpolation.outPosition[0];
|
||||
// player.getPosition().y = interpolation.outPosition[1];
|
||||
// player.getPosition().z = interpolation.outPosition[2];
|
||||
// player.vx = interpolation.outVelocity[0];
|
||||
// player.vy = interpolation.outVelocity[1];
|
||||
// player.vz = interpolation.outVelocity[2];
|
||||
self.lastTime = time;
|
||||
}
|
||||
|
||||
pub fn receiveData(self: *User, data: []const u8) void {
|
||||
const position: [3]f64 = .{
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[0..8])),
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[8..16])),
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[16..24])),
|
||||
};
|
||||
const velocity: [3]f64 = .{
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[24..32])),
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[32..40])),
|
||||
@bitCast(f64, std.mem.readIntBig(u64, data[40..48])),
|
||||
};
|
||||
const rotation: [3]f32 = .{
|
||||
@bitCast(f32, std.mem.readIntBig(u32, data[48..52])),
|
||||
@bitCast(f32, std.mem.readIntBig(u32, data[52..56])),
|
||||
@bitCast(f32, std.mem.readIntBig(u32, data[56..60])),
|
||||
};
|
||||
_ = rotation;
|
||||
// TODO: player.getRotation().set(rotation);
|
||||
const time = std.mem.readIntBig(i16, data[60..62]);
|
||||
self.timeDifference.addDataPoint(time);
|
||||
self.interpolation.updatePosition(&position, &velocity, time);
|
||||
}
|
||||
// TODO (Command stuff):
|
||||
// @Override
|
||||
// public void feedback(String feedback) {
|
||||
// Protocols.CHAT.send(this, "#ffff00"+feedback);
|
||||
// }
|
||||
};
|
||||
|
||||
const updatesPerSec: u32 = 20;
|
||||
const updateNanoTime: u32 = 1000000000/20;
|
||||
// TODO:
|
||||
// public static ServerWorld world = null;
|
||||
pub var users: std.ArrayList(*User) = undefined;
|
||||
|
||||
pub var connectionManager: *ConnectionManager = undefined;
|
||||
|
||||
var running: bool = false;
|
||||
var lastTime: i128 = undefined;
|
||||
|
||||
var mutex: std.Thread.Mutex = .{};
|
||||
|
||||
fn init() !void {
|
||||
users = std.ArrayList(*User).init(main.globalAllocator);
|
||||
lastTime = std.time.nanoTimestamp();
|
||||
connectionManager = try ConnectionManager.init(main.settings.defaultPort, false); // TODO Configure the second argument in the server settings.
|
||||
// TODO: Load the assets.
|
||||
// TODO:
|
||||
// Server.world = new ServerWorld(args[0], null);
|
||||
if(true) { // singleplayer // TODO: Configure this in the server settings.
|
||||
const user = try User.init(connectionManager, "127.0.0.1:47650");
|
||||
try connect(user);
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit() void {
|
||||
for(users.items) |user| {
|
||||
user.deinit();
|
||||
}
|
||||
users.clearAndFree();
|
||||
connectionManager.deinit();
|
||||
connectionManager = undefined;
|
||||
// TODO:
|
||||
// if(world != null)
|
||||
// world.cleanup();
|
||||
// world = null;
|
||||
}
|
||||
|
||||
fn update() !void {
|
||||
// TODO: world.update();
|
||||
mutex.lock();
|
||||
for(users.items) |user| {
|
||||
user.update();
|
||||
}
|
||||
mutex.unlock();
|
||||
// TODO:
|
||||
// Entity[] entities = world.getEntities();
|
||||
// Protocols.ENTITY.sendToClients(entities, lastSentEntities, world.itemEntityManager);
|
||||
// lastSentEntities = entities;
|
||||
}
|
||||
|
||||
pub fn start() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){};
|
||||
main.threadAllocator = gpa.allocator();
|
||||
defer if(gpa.deinit()) {
|
||||
std.log.err("Memory leak", .{});
|
||||
};
|
||||
std.debug.assert(!running); // There can only be one server.
|
||||
try init();
|
||||
defer deinit();
|
||||
running = true;
|
||||
while(running) {
|
||||
const newTime = std.time.nanoTimestamp();
|
||||
if(newTime -% lastTime < updateNanoTime) {
|
||||
std.time.sleep(@intCast(u64, lastTime +% updateNanoTime -% newTime));
|
||||
lastTime +%= updateNanoTime;
|
||||
} else {
|
||||
std.log.warn("The server is lagging behind by {d:.1} ms", .{@intToFloat(f32, newTime -% lastTime -% updateNanoTime)/1000000.0});
|
||||
lastTime = newTime;
|
||||
}
|
||||
try update();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop() void {
|
||||
running = false;
|
||||
}
|
||||
|
||||
pub fn disconnect(user: *User) !void {
|
||||
// TODO: world.forceSave();
|
||||
const message = try std.fmt.allocPrint(main.threadAllocator, "{s} #ffff00left", .{user.name});
|
||||
defer main.threadAllocator.free(message);
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
try sendMessage(message);
|
||||
|
||||
for(users.items, 0..) |other, i| {
|
||||
if(other == user) {
|
||||
_ = users.swapRemove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: world.removeEntity(user.player);
|
||||
// TODO? users = usersList.toArray();
|
||||
}
|
||||
|
||||
pub fn connect(user: *User) !void {
|
||||
const message = try std.fmt.allocPrint(main.threadAllocator, "{s} #ffff00joined", .{user.name});
|
||||
defer main.threadAllocator.free(message);
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
try sendMessage(message);
|
||||
|
||||
try users.append(user);
|
||||
// TODO: users = usersList.toArray();
|
||||
}
|
||||
|
||||
// private Entity[] lastSentEntities = new Entity[0];
|
||||
|
||||
pub fn sendMessage(msg: []const u8) !void {
|
||||
std.debug.assert(!mutex.tryLock()); // Mutex must be locked!
|
||||
std.log.info("Chat: {s}", .{msg}); // TODO use color \033[0;32m
|
||||
for(users.items) |user| {
|
||||
try main.network.Protocols.chat.send(user.conn, msg);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user