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:
IntegratedQuantum 2023-04-09 11:28:21 +02:00
parent f1681d1687
commit b98f565f2e
9 changed files with 359 additions and 52 deletions

View File

@ -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);

View File

@ -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

View File

@ -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| {

View File

@ -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)});

View File

@ -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)});

View File

@ -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);

View File

@ -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");

View File

@ -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
View 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);
}
}