Add some networking code (namely the UDPConnection.java) and create a threadlocal allocator, to make simple allocations easier(without creating/passing around a new allocator every time).

This commit is contained in:
IntegratedQuantum 2022-09-05 21:41:54 +02:00
parent b7889a554d
commit bebc007d74
8 changed files with 806 additions and 120 deletions

View File

@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const json = @import("json.zig");
const JsonElement = json.JsonElement;
const blocks_zig = @import("blocks.zig");
const main = @import("main.zig");
var arena: std.heap.ArenaAllocator = undefined;
var arenaAllocator: Allocator = undefined;
@ -13,11 +14,6 @@ var commonRecipes: std.ArrayList([]const u8) = undefined;
/// Reads json files recursively from all subfolders.
pub fn readAllJsonFilesInAddons(externalAllocator: Allocator, addons: std.ArrayList(std.fs.Dir), addonNames: std.ArrayList([]const u8), subPath: []const u8, output: *std.StringHashMap(JsonElement)) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if(gpa.deinit()) {
@panic("Memory leak");
};
var internalAllocator = gpa.allocator();
for(addons.items) |addon, addonIndex| {
var dir: std.fs.IterableDir = addon.openIterableDir(subPath, .{}) catch |err| {
if(err == error.FileNotFound) continue;
@ -25,7 +21,7 @@ pub fn readAllJsonFilesInAddons(externalAllocator: Allocator, addons: std.ArrayL
};
defer dir.close();
var walker = try dir.walk(internalAllocator);
var walker = try dir.walk(main.threadAllocator);
defer walker.deinit();
while(try walker.next()) |entry| {
@ -39,18 +35,18 @@ pub fn readAllJsonFilesInAddons(externalAllocator: Allocator, addons: std.ArrayL
std.log.info("ID: {s}", .{id});
var file = try dir.dir.openFile(entry.path, .{});
defer file.close();
const string = try file.readToEndAlloc(internalAllocator, std.math.maxInt(usize));
defer internalAllocator.free(string);
const string = try file.readToEndAlloc(main.threadAllocator, std.math.maxInt(usize));
defer main.threadAllocator.free(string);
try output.put(id, json.parseFromString(externalAllocator, string));
}
}
}
}
pub fn readAssets(externalAllocator: Allocator, temporaryAllocator: Allocator, assetPath: []const u8, blocks: *std.StringHashMap(JsonElement), biomes: *std.StringHashMap(JsonElement)) !void {
var addons = std.ArrayList(std.fs.Dir).init(temporaryAllocator);
pub fn readAssets(externalAllocator: Allocator, assetPath: []const u8, blocks: *std.StringHashMap(JsonElement), biomes: *std.StringHashMap(JsonElement)) !void {
var addons = std.ArrayList(std.fs.Dir).init(main.threadAllocator);
defer addons.deinit();
var addonNames = std.ArrayList([]const u8).init(temporaryAllocator);
var addonNames = std.ArrayList([]const u8).init(main.threadAllocator);
defer addonNames.deinit();
{ // Find all the sub-directories to the assets folder.
@ -60,13 +56,13 @@ pub fn readAssets(externalAllocator: Allocator, temporaryAllocator: Allocator, a
while(try iterator.next()) |addon| {
if(addon.kind == .Directory) {
try addons.append(try dir.dir.openDir(addon.name, .{}));
try addonNames.append(try temporaryAllocator.dupe(u8, addon.name));
try addonNames.append(try main.threadAllocator.dupe(u8, addon.name));
}
}
}
defer for(addons.items) |*dir, idx| {
dir.close();
temporaryAllocator.free(addonNames.items[idx]);
main.threadAllocator.free(addonNames.items[idx]);
};
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "blocks", blocks);
@ -74,19 +70,13 @@ pub fn readAssets(externalAllocator: Allocator, temporaryAllocator: Allocator, a
}
pub fn init() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpaAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
};
arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
arenaAllocator = arena.allocator();
commonBlocks = std.StringHashMap(JsonElement).init(arenaAllocator);
commonBiomes = std.StringHashMap(JsonElement).init(arenaAllocator);
commonRecipes = std.ArrayList([]const u8).init(arenaAllocator);
try readAssets(arenaAllocator, gpaAllocator, "assets/", &commonBlocks, &commonBiomes);
try readAssets(arenaAllocator, "assets/", &commonBlocks, &commonBiomes);
}
pub fn registerBlock(assetFolder: []const u8, id: []const u8, info: JsonElement) !void {
@ -140,18 +130,12 @@ pub fn registerBlock(assetFolder: []const u8, id: []const u8, info: JsonElement)
}
pub fn loadWorldAssets(assetFolder: []const u8) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpaAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
};
var blocks = try commonBlocks.cloneWithAllocator(gpaAllocator);
var blocks = try commonBlocks.cloneWithAllocator(main.threadAllocator);
defer blocks.clearAndFree();
var biomes = try commonBiomes.cloneWithAllocator(gpaAllocator);
var biomes = try commonBiomes.cloneWithAllocator(main.threadAllocator);
defer biomes.clearAndFree();
try readAssets(arenaAllocator, gpaAllocator, assetFolder, &blocks, &biomes);
try readAssets(arenaAllocator, assetFolder, &blocks, &biomes);
var block: u32 = 0;
// TODO:

View File

@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const blocks = @import("blocks.zig");
const Block = blocks.Block;
const game = @import("game.zig");
const graphics = @import("graphics.zig");
const c = graphics.c;
const Shader = graphics.Shader;
@ -88,9 +89,9 @@ pub const Chunk = struct {
wasCleaned: bool = false,
generated: bool = false,
width: ChunkPosition,
width: ChunkCoordinate,
voxelSizeShift: u5,
voxelSizeMask: ChunkPosition,
voxelSizeMask: ChunkCoordinate,
widthShift: u5,
mutex: std.Thread.Mutex,
@ -476,7 +477,7 @@ pub const ChunkVisibilityData = struct {
pub const meshing = struct {
var shader: Shader = undefined;
var uniforms: struct {
pub var uniforms: struct {
projectionMatrix: c_int,
viewMatrix: c_int,
modelPosition: c_int,
@ -498,8 +499,6 @@ pub const meshing = struct {
var vbo: c_uint = undefined;
var faces: std.ArrayList(u32) = undefined;
// TODO: public static final Matrix4f projMatrix = new Matrix4f();
pub fn init() !void {
shader = try Shader.create("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs");
uniforms = shader.bulkGetUniformLocation(@TypeOf(uniforms));
@ -531,17 +530,16 @@ pub const meshing = struct {
pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, directional: Vec3f, time: u32) void {
shader.bind();
// TODO:
// shader.setUniform(loc_fog_activ, Cubyz.fog.isActive());
// shader.setUniform(loc_fog_color, Cubyz.fog.getColor());
// shader.setUniform(loc_fog_density, Cubyz.fog.getDensity());
c.glUniform1i(uniforms.fog_activ, if(game.fog.active) 1 else 0);
c.glUniform3fv(uniforms.fog_color, 1, @ptrCast([*c]f32, &game.fog.color));
c.glUniform1f(uniforms.fog_density, game.fog.density);
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]f32, &projMatrix));
// shader.setUniform(loc_texture_sampler, 0);
// shader.setUniform(loc_emissionSampler, 1);
//
// shader.setUniform(loc_viewMatrix, Camera.getViewMatrix());
c.glUniform1i(uniforms.texture_sampler, 0);
c.glUniform1i(uniforms.emissionSampler, 1);
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast([*c]f32, &game.camera.viewMatrix));
c.glUniform3f(uniforms.ambientLight, ambient.x, ambient.y, ambient.z);
c.glUniform3f(uniforms.directionalLight, directional.x, directional.y, directional.z);

View File

@ -3,6 +3,8 @@ const std = @import("std");
const vec = @import("vec.zig");
const Vec3f = vec.Vec3f;
const Mat4f = vec.Mat4f;
const graphics = @import("graphics.zig");
const Fog = graphics.Fog;
pub const camera = struct {
var rotation: Vec3f = Vec3f{.x = 0, .y = 0, .z = 0};
@ -32,4 +34,6 @@ pub var testWorld: World = 0;
pub var world: ?*World = &testWorld;
pub var projectionMatrix: Mat4f = Mat4f.identity();
pub var lodProjectionMatrix: Mat4f = Mat4f.identity();
pub var lodProjectionMatrix: Mat4f = Mat4f.identity();
pub var fog = Fog{.active = true, .color=.{.x=0.5, .y=0.5, .z=0.5}, .density=0.025};

View File

@ -2,10 +2,13 @@
/// Also contains some basic 2d drawing stuff.
const std = @import("std");
const Vec4i = @import("vec.zig").Vec4i;
const Vec2f = @import("vec.zig").Vec2f;
const Vec3f = @import("vec.zig").Vec3f;
const Window = @import("main.zig").Window;
const main = @import("main.zig");
const Window = main.Window;
const Allocator = std.mem.Allocator;
@ -329,11 +332,11 @@ pub const Shader = struct {
id: c_uint,
fn addShader(self: *const Shader, filename: []const u8, shader_stage: c_uint) !void {
const source = fileToString(std.heap.page_allocator, filename) catch |err| {
const source = fileToString(main.threadAllocator, filename) catch |err| {
std.log.warn("Couldn't find file: {s}", .{filename});
return err;
};
defer std.heap.page_allocator.free(source);
defer main.threadAllocator.free(source);
const ref_buffer = [_] [*c]u8 {@ptrCast([*c]u8, source.ptr)};
const shader = c.glCreateShader(shader_stage);
defer c.glDeleteShader(shader);
@ -568,8 +571,8 @@ pub const Image = struct {
pub fn readFromFile(allocator: Allocator, path: []const u8) !Image {
var result: Image = undefined;
var channel: c_int = undefined;
var buffer: [1024]u8 = undefined;
const nullTerminatedPath = try std.fmt.bufPrintZ(&buffer, "{s}", .{path}); // TODO: Find a more zig-friendly image loading library.
const nullTerminatedPath = try std.fmt.allocPrintZ(main.threadAllocator, "{s}", .{path}); // TODO: Find a more zig-friendly image loading library.
defer main.threadAllocator.free(nullTerminatedPath);
const data = stb_image.stbi_load(nullTerminatedPath.ptr, @ptrCast([*c]c_int, &result.width), @ptrCast([*c]c_int, &result.height), &channel, 4) orelse {
return error.FileNotFound;
};
@ -577,4 +580,10 @@ pub const Image = struct {
stb_image.stbi_image_free(data);
return result;
}
};
pub const Fog = struct {
active: bool,
color: Vec3f,
density: f32,
};

View File

@ -5,6 +5,7 @@ const blocks = @import("blocks.zig");
const chunk = @import("chunk.zig");
const graphics = @import("graphics.zig");
const renderer = @import("renderer.zig");
const network = @import("network.zig");
const Vec2f = @import("vec.zig").Vec2f;
@ -13,6 +14,8 @@ pub const c = @cImport ({
@cInclude("GLFW/glfw3.h");
});
pub threadlocal var threadAllocator: std.mem.Allocator = undefined;
var logFile: std.fs.File = undefined;
pub fn log(
@ -27,15 +30,16 @@ pub fn log(
std.log.Level.warn => "\x1b[33m",
std.log.Level.debug => "\x1b[37;44m",
};
var buf: [4096]u8 = undefined;
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const fileMessage = std.fmt.bufPrint(&buf, "[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch return;
const fileMessage = std.fmt.allocPrint(threadAllocator, "[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch return;
defer threadAllocator.free(fileMessage);
logFile.writeAll(fileMessage) catch return;
const terminalMessage = std.fmt.bufPrint(&buf, color ++ format ++ "\x1b[0m\n", args) catch return;
const terminalMessage = std.fmt.allocPrint(threadAllocator, color ++ format ++ "\x1b[0m\n", args) catch return;
defer threadAllocator.free(terminalMessage);
nosuspend std.io.getStdErr().writeAll(terminalMessage) catch return;
}
@ -130,6 +134,12 @@ pub const Window = struct {
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
};
// init logging.
logFile = std.fs.cwd().createFile("logs/latest.log", .{}) catch unreachable;
defer logFile.close();
@ -154,6 +164,9 @@ pub fn main() !void {
try assets.loadWorldAssets("saves");
var conn = try network.ConnectionManager.init(12345, true);
defer conn.deinit();
c.glEnable(c.GL_CULL_FACE);
c.glCullFace(c.GL_BACK);
c.glEnable(c.GL_BLEND);
@ -183,6 +196,9 @@ pub fn main() !void {
graphics.Draw.line(Vec2f{.x = 0, .y = 0}, Vec2f{.x = 1920, .y = 1080});
}
}
var conn2 = try network.Connection.init(conn, "127.0.0.1:12345");
conn2.deinit();
}
test "abc" {

686
src/network.zig Normal file
View File

@ -0,0 +1,686 @@
const std = @import("std");
const main = @import("main.zig");
const game = @import("game.zig");
const settings = @import("settings.zig");
//TODO: Might want to use SSL or something similar to encode the message
const LinuxSocket = struct {
const c = @cImport({
@cInclude("sys/socket.h");
@cInclude("netinet/in.h");
@cInclude("sys/types.h");
@cInclude("unistd.h");
@cInclude("string.h");
@cInclude("errno.h");
@cInclude("stdio.h");
@cInclude("arpa/inet.h");
});
socketID: u31,
fn checkError(comptime msg: []const u8, result: c_int) !u31 {
if(result == -1) {
std.log.warn(msg, .{c.__errno_location().*});
return error.SocketError;
}
return @intCast(u31, result);
}
fn init(localPort: u16) !LinuxSocket {
var socketID: u31 = undefined;
socketID = try checkError("Socket creation failed with error: {}", c.socket(c.AF_INET, c.SOCK_DGRAM, c.IPPROTO_UDP));
errdefer _ = checkError("Error while closing socket: {}", c.close(socketID)) catch 0;
var bindingAddr: c.sockaddr_in = undefined;
bindingAddr.sin_family = c.AF_INET;
bindingAddr.sin_port = c.htons(localPort);
bindingAddr.sin_addr.s_addr = c.inet_addr("127.0.0.1");
bindingAddr.sin_zero = [_]u8{0} ** 8;
_ = try checkError("Socket binding failed with error: {}", c.bind(socketID, @ptrCast(*c.sockaddr, &bindingAddr), @sizeOf(c.sockaddr_in))); // TODO: Use the next higher port, when the port is already in use.
return LinuxSocket{.socketID = socketID};
}
fn deinit(self: LinuxSocket) void {
_ = checkError("Error while closing socket: {}", c.close(self.socketID)) catch 0;
}
};
pub const Address = struct {
ip: []const u8,
port: u16,
};
pub const ConnectionManager = struct {
socket: LinuxSocket = undefined,
thread: std.Thread = undefined,
online: bool = false,
pub fn init(localPort: u16, online: bool) !ConnectionManager {
_ = online; //TODO
var result = ConnectionManager{};
result.socket = try LinuxSocket.init(localPort);
errdefer LinuxSocket.deinit(result.socket);
result.thread = try std.Thread.spawn(.{}, run, .{result});
if(online) {
result.makeOnline();
}
return result;
}
pub fn deinit(self: ConnectionManager) void {
LinuxSocket.deinit(self.socket);
self.thread.join();
}
pub fn makeOnline(self: *ConnectionManager) void {
if(!self.online) {
// TODO:
// externalIPPort = STUN.requestIPPort(this);
// String[] ipPort;
// if(externalIPPort.contains("?")) {
// ipPort = externalIPPort.split(":\\?");
// } else {
// ipPort = externalIPPort.split(":");
// }
// try {
// externalAddress = InetAddress.getByName(ipPort[0]);
// } catch(UnknownHostException e) {
// Logger.error(e);
// throw new IllegalArgumentException("externalIPPort is invalid.");
// }
// externalPort = Integer.parseInt(ipPort[1]);
self.online = true;
}
}
pub fn run(self: ConnectionManager) void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
main.threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
};
_ = self; // TODO
}
pub fn send(self: ConnectionManager, data: []const u8, target: Address) void {
// TODO
_ = self;
_ = data;
_ = target;
}
};
// sockID = c.socket(c.AF_INET, c.SOCK_DGRAM, c.IPPROTO_UDP);
// defer _ = c.close(sockID);
// _ = c.memset(&otherAddr, 0, @sizeOf(c.sockaddr_in));
// otherAddr.sin_family = c.AF_INET;
// otherAddr.sin_port = c.htons(40001);
// otherAddr.sin_addr.s_addr = c.inet_addr("???.???.???.???");
// var myAddr: c.sockaddr_in = undefined;
// _ = c.memset(&myAddr, 0, @sizeOf(c.sockaddr_in));
// myAddr.sin_family = c.AF_INET;
// myAddr.sin_port = c.htons(40001);
// myAddr.sin_addr.s_addr = c.inet_addr("192.168.178.60");
//
// _ = errorCheck(c.bind(sockID, @ptrCast(*c.sockaddr, &myAddr), @sizeOf(c.sockaddr_in)));
//
// _ = std.Thread.spawn(.{}, keepAlive, .{}) catch null;
//public final class UDPConnectionManager extends Thread {
// private final DatagramPacket receivedPacket;
// public final ArrayList<UDPConnection> connections = new ArrayList<>();
// private final ArrayList<DatagramPacket> requests = new ArrayList<>();
// private volatile boolean running = true;
// public String externalIPPort = null;
// private InetAddress externalAddress = null;
// private int externalPort = 0;
//
// public void send(DatagramPacket packet) {
// try {
// socket.send(packet);
// } catch(IOException e) {
// Logger.error(e);
// }
// }
//
// public byte[] sendRequest(DatagramPacket packet, long timeout) {
// send(packet);
// byte[] request = packet.getData();
// synchronized(requests) {
// requests.add(packet);
// }
// synchronized(packet) {
// try {
// packet.wait(timeout);
// } catch(InterruptedException e) {}
// }
// synchronized(requests) {
// requests.remove(packet);
// }
// if(packet.getData() == request) {
// return null;
// } else {
// return packet.getData();
// }
// }
//
// public void addConnection(UDPConnection connection) {
// synchronized(connections) {
// connections.add(connection);
// }
// }
//
// public void removeConnection(UDPConnection connection) {
// synchronized(connections) {
// connections.remove(connection);
// }
// }
//
// public void cleanup() {
// while(!connections.isEmpty()) {
// connections.get(0).disconnect();
// }
// running = false;
// if(Thread.currentThread() != this) {
// interrupt();
// try {
// join();
// } catch(InterruptedException e) {
// Logger.error(e);
// }
// }
// socket.close();
// }
//
// private void onReceive() {
// byte[] data = receivedPacket.getData();
// int len = receivedPacket.getLength();
// InetAddress addr = receivedPacket.getAddress();
// int port = receivedPacket.getPort();
// for(UDPConnection connection : connections) {
// if(connection.remoteAddress.equals(addr)) {
// if(connection.bruteforcingPort) { // brute-forcing the port was successful.
// connection.remotePort = port;
// connection.bruteforcingPort = false;
// }
// if(connection.remotePort == port) {
// connection.receive(data, len);
// return;
// }
// }
// }
// // Check if it's part of an active request:
// synchronized(requests) {
// for(DatagramPacket packet : requests) {
// if(packet.getAddress().equals(addr) && packet.getPort() == port) {
// packet.setData(Arrays.copyOf(data, len));
// synchronized(packet) {
// packet.notify();
// }
// return;
// }
// }
// }
// if(addr.equals(externalAddress) && port == externalPort) return;
// if(addr.toString().contains("127.0.0.1")) return;
// Logger.warning("Unknown connection from address: " + addr+":"+port);
// Logger.debug("Message: "+Arrays.toString(Arrays.copyOf(data, len)));
// }
//
// @Override
// public void run() {
// assert Thread.currentThread() == this : "UDPConnectionManager.run() shouldn't be called by anyone.";
// try {
// socket.setSoTimeout(100);
// long lastTime = System.currentTimeMillis();
// while (running) {
// try {
// socket.receive(receivedPacket);
// onReceive();
// } catch(SocketTimeoutException e) {
// // No message within the last ~100 ms.
// }
//
// // Send a keep-alive packet roughly every 100 ms:
// if(System.currentTimeMillis() - lastTime > 100 && running) {
// lastTime = System.currentTimeMillis();
// for(UDPConnection connection : connections.toArray(new UDPConnection[0])) {
// if(lastTime - connection.lastConnection > CONNECTION_TIMEOUT && connection.isConnected()) {
// Logger.info("timeout");
// // Timeout a connection if it was connect at some point. New connections are not timed out because that could annoy players(having to restart the connection several times).
// connection.disconnect();
// } else {
// connection.sendKeepAlive();
// }
// }
// if(connections.isEmpty() && externalAddress != null) {
// // Send a message to external ip, to keep the port open:
// DatagramPacket packet = new DatagramPacket(new byte[0], 0);
// packet.setAddress(externalAddress);
// packet.setPort(externalPort);
// packet.setLength(0);
// send(packet);
// }
// }
// }
// } catch (Exception e) {
// Logger.crash(e);
// }
// }
//}
const UnconfirmedPacket = struct {
data: []const u8,
lastKeepAliveSentBefore: u32,
id: u32,
};
const Protocol = struct {
id: u8,
const keepAlive: u8 = 0;
const important: u8 = 0xff;
}; // TODO
pub const Connection = struct {
const maxPacketSize: u32 = 65507; // max udp packet size
const importantHeaderSize: u32 = 5;
const maxImportantPacketSize: u32 = 1500 - 20 - 8; // Ethernet MTU minus IP header minus udp header
// Statistics:
var packetsSent: u32 = 0;
var packetsResent: u32 = 0;
manager: ConnectionManager,
gpa: std.heap.GeneralPurposeAllocator(.{}),
allocator: std.mem.Allocator,
remoteAddress: Address,
bruteforcingPort: bool = false,
bruteForcedPortRange: u16 = 0,
streamBuffer: [maxImportantPacketSize]u8 = undefined,
streamPosition: u32 = importantHeaderSize,
messageID: u32 = 0,
unconfirmedPackets: std.ArrayList(UnconfirmedPacket),
receivedPackets: [3]std.ArrayList(u32),
lastReceivedPackets: [65536]?[]const u8 = undefined,
lastIndex: u32 = 0,
lastIncompletePacket: u32 = 0,
lastKeepAliveSent: u32 = 0,
lastKeepAliveReceived: u32 = 0,
otherKeepAliveReceived: u32 = 0,
disconnected: bool = false,
handShakeComplete: bool = false,
lastConnection: i64 = 0,
mutex: std.Thread.Mutex = std.Thread.Mutex{},
pub fn init(manager: ConnectionManager, ipPort: []const u8) !*Connection {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var result: *Connection = try gpa.allocator().create(Connection);
result.* = Connection {
.manager = manager,
.gpa = gpa,
.allocator = undefined,
.remoteAddress = undefined,
.unconfirmedPackets = std.ArrayList(UnconfirmedPacket).init(gpa.allocator()),
.receivedPackets = [3]std.ArrayList(u32){
std.ArrayList(u32).init(gpa.allocator()),
std.ArrayList(u32).init(gpa.allocator()),
std.ArrayList(u32).init(gpa.allocator()),
},
};
result.allocator = result.gpa.allocator(); // The right reference(the one that isn't on the stack) needs to be used passed!
var splitter = std.mem.split(u8, ipPort, ":");
result.remoteAddress.ip = try result.allocator.dupe(u8, splitter.first());
var port = splitter.rest();
if(port.len != 0 and port[0] == '?') {
result.bruteforcingPort = true;
port = port[1..];
}
result.remoteAddress.port = std.fmt.parseUnsigned(u16, port, 10) catch blk: {
std.log.warn("Could not parse port \"{s}\". Using default port instead.", .{port});
break :blk settings.defaultPort;
};
// TODO: manager.addConnection(this);
return result;
}
pub fn deinit(self: *Connection) void {
self.unconfirmedPackets.deinit();
self.receivedPackets[0].deinit();
self.receivedPackets[1].deinit();
self.receivedPackets[2].deinit();
self.allocator.free(self.remoteAddress.ip);
var gpa = self.gpa;
gpa.allocator().destroy(self);
if(gpa.deinit()) {
@panic("Memory leak in connection.");
}
}
fn flush(self: *Connection) !void {
self.mutex.lock();
defer self.mutex.unlock();
if(self.streamPosition == importantHeaderSize) return; // Don't send empty packets.
// Fill the header:
self.streamBuffer[0] = Protocol.important;
var id = self.messageID;
self.messageID += 1;
std.mem.writeIntBig(u32, self.streamBuffer[1..5], id); // TODO: Use little endian for better hardware support. Currently the aim is interoperability with the java version which uses big endian.
var packet = UnconfirmedPacket{
.data = try self.allocator.dupe(u8, self.streamBuffer[0..self.streamPosition]),
.lastKeepAliveSentBefore = self.lastKeepAliveSent,
.id = id,
};
try self.unconfirmedPackets.append(packet);
packetsSent += 1;
self.manager.send(packet.data, self.remoteAddress);
self.streamPosition = importantHeaderSize;
}
fn writeByteToStream(self: *Connection, data: u8) void {
self.streamBuffer[self.streamPosition] = data;
self.streamPosition += 1;
if(self.streamPosition == self.streamBuffer.length) {
self.flush();
}
}
pub fn sendImportant(self: *Connection, source: Protocol, data: []const u8) void {
self.mutex.lock();
defer self.mutex.unlock();
if(self.disconnected) return;
self.writeByteToStream(source.id);
var processedLength = data.len;
while(processedLength > 0x7f) {
self.writeByteToStream(@intCast(u8, processedLength & 0x7f) | 0x80);
processedLength >>= 7;
}
self.writeByteToStream(@intCast(u8, processedLength & 0x7f));
var remaining: []const u8 = data;
while(remaining.len != 0) {
var copyableSize = @minimum(remaining.len, self.streamBuffer.len - self.streamPosition);
std.mem.copy(u8, self.streamBuffer, remaining[0..copyableSize]);
remaining = remaining[copyableSize..];
self.streamPosition += copyableSize;
if(self.streamPosition == self.streamBuffer.len) {
self.flush();
}
}
}
pub fn sendUnimportant(self: *Connection, source: Protocol, data: []const u8) !void {
self.mutex.lock();
defer self.mutex.unlock();
if(self.disconnected) return;
std.debug.assert(data.len + 1 < maxPacketSize);
var fullData = try main.threadAllocator.alloc(u8, data.len + 1);
defer main.threadAllocator.free(fullData);
fullData[0] = source.id;
std.mem.copy(u8, fullData[1..], data);
self.manager.send(fullData, self.remoteAddress);
}
fn receiveKeepAlive(self: *Connection, data: []const u8) void {
self.mutex.lock();
defer self.mutex.unlock();
self.otherKeepAliveReceived = std.mem.readIntBig(u32, data[0..4]);
self.lastKeepAliveReceived = std.mem.readIntBig(u32, data[4..8]);
var remaining: []const u8 = data[8..];
while(remaining.len >= 8) {
var start = std.mem.readIntBig(u32, data[0..4]);
var len = std.mem.readIntBig(u32, data[4..8]);
var j: usize = 0;
while(j < self.unconfirmedPackets.items.len): (j += 1) {
var diff = self.unconfirmedPackets.items[j].id -% start;
if(diff < len) {
_ = self.unconfirmedPackets.swapRemove(j);
j -= 1;
}
}
}
}
fn sendKeepAlive(self: *Connection) void {
self.mutex.lock();
defer self.mutex.unlock();
var runLengthEncodingStarts: std.ArrayList(u32) = std.ArrayList(u32).init(main.threadAllocator);
defer runLengthEncodingStarts.deinit();
var runLengthEncodingLengths: std.ArrayList(u32) = std.ArrayList(u32).init(main.threadAllocator);
defer runLengthEncodingLengths.deinit();
for(self.receivedPackets) |list| {
for(list.items) |packetID| {
var leftRegion: ?u32 = null;
var rightRegion: ?u32 = null;
for(runLengthEncodingStarts) |start, reg| {
var diff = packetID -% start;
if(diff < runLengthEncodingLengths.items[reg]) continue;
if(diff == runLengthEncodingLengths.items[reg]) {
leftRegion = reg;
}
if(diff == std.math.maxInt(u32)) {
rightRegion == reg;
}
}
if(leftRegion) |left| {
if(rightRegion) |right| {
// Needs to combine the regions:
runLengthEncodingLengths.items[left] += runLengthEncodingLengths.items[right] + 1;
runLengthEncodingStarts.swapRemove(right);
runLengthEncodingLengths.swapRemove(right);
} else {
runLengthEncodingLengths.items[left] += 1;
}
} else if(rightRegion) |right| {
runLengthEncodingStarts.items[right] -= 1;
runLengthEncodingLengths.items[right] += 1;
} else {
try runLengthEncodingStarts.append(packetID);
try runLengthEncodingLengths.append(1);
}
}
}
{ // Cycle the receivedPackets lists:
var putBackToFront: std.ArrayList(u32) = self.receivedPackets[self.receivedPackets.len - 1];
var i: u32 = self.receivedPackets.len - 1;
while(i >= 1): (i -= 1) {
self.receivedPackets[i] = self.receivedPackets[i-1];
}
self.receivedPackets[0] = putBackToFront;
self.receivedPackets[0].clearRetainingCapacity();
}
var output = try main.threadAllocator.alloc(u8, runLengthEncodingStarts.items.len*8 + 9);
defer main.threadAllocator.free(output);
output[0] = Protocol.keepAlive;
std.mem.writeIntBig(u32, output[1..5], self.lastKeepAliveSent);
self.lastKeepAliveSent += 1;
std.mem.writeIntBig(u32, output[5..9], self.otherKeepAliveReceived);
var remaining: []const u8 = output[9..];
for(runLengthEncodingStarts) |_, i| {
std.mem.writeIntBig(u32, remaining[0..4], self.runLengthEncodingStarts.items[i]);
std.mem.writeIntBig(u32, remaining[4..8], self.runLengthEncodingLengths.items[i]);
remaining = remaining[8..];
}
self.manager.send(output, self.remoteAddress);
// Resend packets that didn't receive confirmation within the last 2 keep-alive signals.
for(self.unconfirmedPackets.items) |*packet| {
if(self.lastKeepAliveReceived - packet.lastKeepAliveSentBefore >= 2) {
packetsSent += 1;
packetsResent += 1;
self.manager.send(packet.data, self.remoteAddress);
packet.lastKeepAliveSentBefore = self.lastKeepAliveSent;
}
}
self.flush();
if(self.bruteforcingPort) {
// This is called every 100 ms, so if I send 10 requests it shouldn't be too bad.
var i: u16 = 0;
while(i < 5): (i += 1) {
var data = [0]u8{};
if(self.remoteAddress.port +% self.bruteForcedPortRange != 0) {
self.manager.send(data, Address{self.remoteAddress.ip, self.remoteAddress.port +% self.bruteForcedPortRange});
}
if(self.remoteAddress.port - self.bruteForcedPortRange != 0) {
self.manager.send(data, Address{self.remoteAddress.ip, self.remoteAddress.port -% self.bruteForcedPortRange});
}
self.bruteForcedPortRange +%= 1;
}
}
}
pub fn isConnected(self: *Connection) bool {
self.mutex.lock();
defer self.mutex.unlock();
return self.otherKeepAliveReceived != 0;
}
fn collectPackets(self: *Connection) !void {
self.mutex.lock();
defer self.mutex.unlock();
while(true) {
var id = self.lastIncompletePacket;
var receivedPacket = self.lastReceivedPackets[id & 65535] orelse return;
var newIndex = self.lastIndex;
var protocol = receivedPacket[newIndex];
newIndex += 1;
// TODO:
_ = protocol;
// if(Cubyz.world == null && protocol != Protocols.HANDSHAKE.id)
// return;
// Determine the next packet length:
var len: u32 = 0;
var shift: u32 = 0;
while(true) {
if(newIndex == receivedPacket.len) {
newIndex = 0;
id += 1;
receivedPacket = self.lastReceivedPackets[id & 65535] orelse return;
}
var nextByte = receivedPacket[newIndex];
newIndex += 1;
len |= (nextByte & 0x7f) << shift;
if(nextByte & 0x80 != 0) {
shift += 7;
} else {
break;
}
}
// Check if there is enough data available to fill the packets needs:
var dataAvailable = receivedPacket.len - newIndex;
var idd = id + 1;
while(dataAvailable < len): (idd += 1) {
var otherPacket = self.lastReceivedPackets[idd & 65535] orelse return;
dataAvailable += otherPacket.len;
}
// Copy the data to an array:
var data = try main.threadAllocator.alloc(u8, len);
defer main.threadAllocator.free(data);
var remaining = data[0..];
while(remaining.len != 0) {
dataAvailable = @minimum(self.lastReceivedPackets[id & 65535].?.len - newIndex, remaining.len);
std.mem.copy(u8, remaining, self.lastReceivedPackets[id & 65535].?[newIndex..dataAvailable]);
newIndex += dataAvailable;
remaining = remaining[dataAvailable..];
if(newIndex == self.lastReceivedPackets[id & 65535].?.len) {
id += 1;
newIndex = 0;
}
}
while(self.lastIncompletePacket != id): (self.lastIncompletePacket += 1) {
self.allocator.free(self.lastReceivedPackets[self.lastIncompletePacket & 65535].?);
self.lastReceivedPackets[self.lastIncompletePacket & 65535] = null;
}
self.lastIndex = newIndex;
// TODO:
// Protocols.bytesReceived[protocol & 0xff] += data.length + 1;
// Protocols.list[protocol].receive(this, data, 0, data.length);
}
}
pub fn receive(self: *Connection, data: []const u8) void {
self.mutex.lock();
defer self.mutex.unlock();
const protocol = data[0];
// TODO:
//if(!self.handShakeComplete and protocol != Protocols.HANDSHAKE.id and protocol != Protocol.KEEP_ALIVE and protocol != Protocol.important) {
// return; // Reject all non-handshake packets until the handshake is done.
//}
self.lastConnection = std.time.milliTimestamp();
// TODO:
// Protocols.bytesReceived[protocol & 0xff] += len + 20 + 8; // Including IP header and udp header
// Protocols.packetsReceived[protocol & 0xff]++;
if(protocol == Protocol.important) {
var id = std.mem.readIntBig(u32, data[1..5]);
if(self.handShakeComplete and id == 0) { // Got a new "first" packet from client. So the client tries to reconnect, but we still think it's connected.
// TODO:
// if(this instanceof User) {
// Server.disconnect((User)this);
// disconnected = true;
// manager.removeConnection(this);
// new Thread(() -> {
// try {
// Server.connect(new User(manager, remoteAddress.getHostAddress() + ":" + remotePort));
// } catch(Throwable e) {
// Logger.error(e);
// }
// }).start();
// return;
// } else {
// Logger.error("Server 'reconnected'? This makes no sense and the game can't handle that.");
// }
}
if(id - self.lastIncompletePacket >= 65536) {
std.log.warn("Many incomplete packages. Cannot process any more packages for now.", .{});
return;
}
try self.receivedPackets[0].append(id);
if(id < self.lastIncompletePacket or self.lastReceivedPackets[id & 65535] != null) {
return; // Already received the package in the past.
}
self.lastReceivedPackets[id & 65535] = self.allocator.dupe(data[importantHeaderSize..]);
// Check if a message got completed:
self.collectPackets();
} else if(protocol == Protocol.keepAlive) {
self.receiveKeepAlive(data[1..]);
} else {
// TODO: Protocols.list[protocol & 0xff].receive(this, data, 1, len - 1);
}
}
pub fn disconnect(self: *Connection) void {
// Send 3 disconnect packages to the other side, just to be sure.
// If all of them don't get through then there is probably a network issue anyways which would lead to a timeout.
// TODO:
// Protocols.DISCONNECT.disconnect(this);
// try {Thread.sleep(10);} catch(Exception e) {}
// Protocols.DISCONNECT.disconnect(this);
// try {Thread.sleep(10);} catch(Exception e) {}
// Protocols.DISCONNECT.disconnect(this);
self.disconnected = true;
// TODO: manager.removeConnection(self);
std.log.info("Disconnected");
}
};

View File

@ -3,6 +3,7 @@ const std = @import("std");
const blocks = @import("blocks.zig");
const graphics = @import("graphics.zig");
const c = graphics.c;
const Fog = graphics.Fog;
const Shader = graphics.Shader;
const vec = @import("vec.zig");
const Vec3f = vec.Vec3f;
@ -224,7 +225,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// frustumInt.set(frustumMatrix);
const time = @intCast(u32, std.time.milliTimestamp() & std.math.maxInt(u32));
//TODO: Fog waterFog = new Fog(true, new Vector3f(0.0f, 0.1f, 0.2f), 0.1f);
const waterFog = Fog{.active=true, .color=.{.x=0.0, .y=0.1, .z=0.2}, .density=0.1};
// Update the uniforms. The uniforms are needed to render the replacement meshes.
chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, directionalLight, time);
@ -242,78 +243,63 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// }
c.glDepthRange(0, 0.05);
//
// SimpleList<NormalChunkMesh> visibleChunks = new SimpleList<NormalChunkMesh>(new NormalChunkMesh[64]);
// SimpleList<ReducedChunkMesh> visibleReduced = new SimpleList<ReducedChunkMesh>(new ReducedChunkMesh[64]);
// for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
// if (mesh instanceof NormalChunkMesh) {
// visibleChunks.add((NormalChunkMesh)mesh);
//
// mesh.render(playerPosition);
// } else if (mesh instanceof ReducedChunkMesh) {
// visibleReduced.add((ReducedChunkMesh)mesh);
// }
// SimpleList<NormalChunkMesh> visibleChunks = new SimpleList<NormalChunkMesh>(new NormalChunkMesh[64]);
// SimpleList<ReducedChunkMesh> visibleReduced = new SimpleList<ReducedChunkMesh>(new ReducedChunkMesh[64]);
// for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
// if (mesh instanceof NormalChunkMesh) {
// visibleChunks.add((NormalChunkMesh)mesh);
//
// mesh.render(playerPosition);
// } else if (mesh instanceof ReducedChunkMesh) {
// visibleReduced.add((ReducedChunkMesh)mesh);
// }
// if(selected != null && !Blocks.transparent(selected.getBlock())) {
// BlockBreakingRenderer.render(selected, playerPosition);
// glActiveTexture(GL_TEXTURE0);
// Meshes.blockTextureArray.bind();
// glActiveTexture(GL_TEXTURE1);
// Meshes.emissionTextureArray.bind();
// }
//
// // Render the far away ReducedChunks:
// glDepthRangef(0.05f, 1.0f); // Used to fix z-fighting.
// ReducedChunkMesh.bindShader(ambientLight, directionalLight.getDirection(), time);
// ReducedChunkMesh.shader.setUniform(ReducedChunkMesh.loc_waterFog_activ, waterFog.isActive());
// ReducedChunkMesh.shader.setUniform(ReducedChunkMesh.loc_waterFog_color, waterFog.getColor());
// ReducedChunkMesh.shader.setUniform(ReducedChunkMesh.loc_waterFog_density, waterFog.getDensity());
//
// }
// if(selected != null && !Blocks.transparent(selected.getBlock())) {
// BlockBreakingRenderer.render(selected, playerPosition);
c.glActiveTexture(c.GL_TEXTURE0);
blocks.meshes.blockTextureArray.bind();
c.glActiveTexture(c.GL_TEXTURE1);
blocks.meshes.emissionTextureArray.bind();
// }
// Render the far away ReducedChunks:
c.glDepthRangef(0.05, 1.0); // Used to fix z-fighting.
chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, directionalLight, time);
c.glUniform1i(chunk.meshing.uniforms.waterFog_activ, if(waterFog.active) 1 else 0);
c.glUniform3fv(chunk.meshing.uniforms.waterFog_color, 1, @ptrCast([*c]f32, &waterFog.color));
c.glUniform1f(chunk.meshing.uniforms.waterFog_density, waterFog.density);
// for(int i = 0; i < visibleReduced.size; i++) {
// ReducedChunkMesh mesh = visibleReduced.array[i];
// mesh.render(playerPosition);
// }
// glDepthRangef(0, 0.05f);
//
c.glDepthRangef(0, 0.05);
// EntityRenderer.render(ambientLight, directionalLight, playerPosition);
//
// BlockDropRenderer.render(frustumInt, ambientLight, directionalLight, playerPosition);
//
// /*NormalChunkMesh.shader.bind();
// NormalChunkMesh.shader.setUniform(NormalChunkMesh.loc_fog_activ, 0); // manually disable the fog
// for (int i = 0; i < spatials.length; i++) {
// Spatial spatial = spatials[i];
// Mesh mesh = spatial.getMesh();
// EntityRenderer.entityShader.setUniform(EntityRenderer.loc_light, new Vector3f(1, 1, 1));
// EntityRenderer.entityShader.setUniform(EntityRenderer.loc_materialHasTexture, mesh.getMaterial().isTextured());
// mesh.renderOne(() -> {
// Matrix4f modelViewMatrix = Transformation.getModelViewMatrix(
// Transformation.getModelMatrix(spatial.getPosition(), spatial.getRotation(), spatial.getScale()),
// Camera.getViewMatrix());
// EntityRenderer.entityShader.setUniform(EntityRenderer.loc_viewMatrix, modelViewMatrix);
// });
// }*/ // TODO: Draw the sun.
//
// // Render transparent chunk meshes:
// NormalChunkMesh.bindTransparentShader(ambientLight, directionalLight.getDirection(), time);
//
// buffers.bindTextures();
//
buffers.bindTextures();
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_activ, waterFog.isActive());
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_color, waterFog.getColor());
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_density, waterFog.getDensity());
//
// NormalChunkMesh[] meshes = sortChunks(visibleChunks.toArray(), x0/Chunk.chunkSize - 0.5f, y0/Chunk.chunkSize - 0.5f, z0/Chunk.chunkSize - 0.5f);
// for (NormalChunkMesh mesh : meshes) {
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_drawFrontFace, false);
// glCullFace(GL_FRONT);
// mesh.renderTransparent(playerPosition);
//
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_drawFrontFace, true);
// glCullFace(GL_BACK);
// mesh.renderTransparent(playerPosition);
// }
//
// if(selected != null && Blocks.transparent(selected.getBlock())) {
// BlockBreakingRenderer.render(selected, playerPosition);
// glActiveTexture(GL_TEXTURE0);
@ -321,9 +307,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// glActiveTexture(GL_TEXTURE1);
// Meshes.emissionTextureArray.bind();
// }
//
// fogShader.bind();
// // Draw the water fog if the player is underwater:
fogShader.bind();
// Draw the water fog if the player is underwater:
// Player player = Cubyz.player;
// int block = Cubyz.world.getBlock((int)Math.round(player.getPosition().x), (int)(player.getPosition().y + player.height), (int)Math.round(player.getPosition().z));
// if (block != 0 && !Blocks.solid(block)) {
@ -333,7 +319,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// fogShader.setUniform(FogUniforms.loc_fog_density, waterFog.getDensity());
// glUniform1i(FogUniforms.loc_color, 3);
// glUniform1i(FogUniforms.loc_position, 4);
//
// glBindVertexArray(Graphics.rectVAO);
// glDisable(GL_DEPTH_TEST);
// glDisable(GL_CULL_FACE);
@ -343,22 +329,22 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// if(ClientSettings.BLOOM) {
// BloomRenderer.render(buffers, Window.getWidth(), Window.getHeight()); // TODO: Use true width/height
// }
buffers.unbind();
buffers.bindTextures();
deferredRenderPassShader.bind();
c.glUniform1i(deferredUniforms.color, 3);
c.glUniform1i(deferredUniforms.position, 4);
buffers.unbind();
buffers.bindTextures();
deferredRenderPassShader.bind();
c.glUniform1i(deferredUniforms.color, 3);
c.glUniform1i(deferredUniforms.position, 4);
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().bind();
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().bind();
c.glBindVertexArray(graphics.Draw.rectVAO);
c.glDisable(c.GL_DEPTH_TEST);
c.glDisable(c.GL_CULL_FACE);
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
c.glBindVertexArray(graphics.Draw.rectVAO);
c.glDisable(c.GL_DEPTH_TEST);
c.glDisable(c.GL_CULL_FACE);
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().unbind();
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().unbind();
//TODO EntityRenderer.renderNames(playerPosition);
}

3
src/settings.zig Normal file
View File

@ -0,0 +1,3 @@
pub const defaultPort: u16 = 47649;