diff --git a/src/chunk.zig b/src/chunk.zig index df405631..4bb62e81 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -992,6 +992,28 @@ pub const meshing = struct { for(self.lightingData[3..]) |*lightingData| { try lightingData.propagateLights(lightEmittingBlocks.items, true); } + sunLight: { + var sunStarters: [chunkSize*chunkSize][3]u8 = undefined; + var index: usize = 0; + const lightStartMap = renderer.RenderStructure.getLightMapPieceAndIncreaseRefCount(self.pos.wx, self.pos.wz, self.pos.voxelSize) orelse break :sunLight; + defer lightStartMap.decreaseRefCount(); + x = 0; + while(x < chunkSize): (x += 1) { + var z: u8 = 0; + while(z < chunkSize): (z += 1) { + const startHeight: i32 = lightStartMap.getHeight(self.pos.wx + x*self.pos.voxelSize, self.pos.wz + z*self.pos.voxelSize); + const relHeight = startHeight -% self.pos.wy; + if(relHeight < chunkSize*self.pos.voxelSize) { + sunStarters[index] = .{x, chunkSize-1, z}; + index += 1; + } + } + } + for(self.lightingData[0..3]) |*lightingData| { + try lightingData.propagateLights(sunStarters[0..index], true); + } + } + // TODO: Sunlight propagation try self.finishNeighbors(false); } diff --git a/src/lighting.zig b/src/lighting.zig index c9f71eea..33632703 100644 --- a/src/lighting.zig +++ b/src/lighting.zig @@ -20,6 +20,13 @@ const Channel = enum(u8) { .blue, .sun_blue => return 0, } } + + pub fn isSun(self: Channel) bool { + switch(self) { + .sun_red, .sun_green, .sun_blue => return true, + .red, .green, .blue => return false, + } + } }; pub const ChannelChunk = struct { @@ -61,7 +68,9 @@ pub const ChannelChunk = struct { const ny = entry.y + chunk.Neighbors.relY[neighbor]; const nz = entry.z + chunk.Neighbors.relZ[neighbor]; var result: Entry = .{.x = @intCast(nx & chunk.chunkMask), .y = @intCast(ny & chunk.chunkMask), .z = @intCast(nz & chunk.chunkMask), .value = entry.value}; - result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + if(!self.channel.isSun() or neighbor != chunk.Neighbors.dirDown or result.value != 255) { + result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + } if(result.value == 0) continue; if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { try neighborLists[neighbor].append(main.globalAllocator, result); @@ -106,7 +115,11 @@ pub const ChannelChunk = struct { defer lightQueue.deinit(); for(lights) |pos| { const index = chunk.getIndex(pos[0], pos[1], pos[2]); - try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)}); + if(self.channel.isSun()) { + try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = 255}); + } else { + try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)}); + } } if(checkNeighbors) { for(0..6) |neighbor| { @@ -140,7 +153,9 @@ pub const ChannelChunk = struct { const index = chunk.getIndex(x, y, z); const neighborIndex = chunk.getIndex(otherX, otherY, otherZ); var value: u8 = neighborLightChunk.data[neighborIndex].load(.Unordered); - value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + if(!self.channel.isSun() or neighbor != chunk.Neighbors.dirUp or value != 255) { + value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + } if(value == 0) continue; var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255); absorption *|= @intCast(self.ch.pos.voxelSize); diff --git a/src/network.zig b/src/network.zig index 379fce39..12f317e4 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1225,6 +1225,79 @@ pub const Protocols = struct { try conn.sendImportant(id, data); } }; + pub const lightMapRequest = struct { + pub const id: u8 = 11; + fn receive(conn: *Connection, data: []const u8) !void { + var remaining = data[0..]; + while(remaining.len >= 9) { + const request = main.server.terrain.SurfaceMap.MapFragmentPosition{ + .wx = std.mem.readInt(i32, remaining[0..4], .big), + .wz = std.mem.readInt(i32, remaining[4..8], .big), + .voxelSize = @as(u31, 1) << @intCast(std.mem.readInt(u8, remaining[8..9], .big)), + .voxelSizeShift = @intCast(std.mem.readInt(u8, remaining[8..9], .big)), + }; + if(conn.user) |user| { + try main.server.world.?.queueLightMap(request, user); + } + remaining = remaining[9..]; + } + } + pub fn sendRequest(conn: *Connection, requests: []main.server.terrain.SurfaceMap.MapFragmentPosition) !void { + if(requests.len == 0) return; + const data = try main.stackAllocator.alloc(u8, 9*requests.len); + defer main.stackAllocator.free(data); + var remaining = data; + for(requests) |req| { + std.mem.writeInt(i32, remaining[0..4], req.wx, .big); + std.mem.writeInt(i32, remaining[4..8], req.wz, .big); + std.mem.writeInt(u8, remaining[8..9], req.voxelSizeShift, .big); + remaining = remaining[9..]; + } + try conn.sendImportant(id, data); + } + }; + pub const lightMapTransmission = struct { + pub const id: u8 = 12; + fn receive(_: *Connection, _data: []const u8) !void { + var data = _data; + const pos = main.server.terrain.SurfaceMap.MapFragmentPosition{ + .wx = std.mem.readInt(i32, data[0..4], .big), + .wz = std.mem.readInt(i32, data[4..8], .big), + .voxelSize = @as(u31, 1) << @intCast(std.mem.readInt(u8, data[8..9], .big)), + .voxelSizeShift = @intCast(std.mem.readInt(u8, data[8..9], .big)), + }; + const _inflatedData = try main.stackAllocator.alloc(u8, main.server.terrain.LightMap.LightMapFragment.mapSize*main.server.terrain.LightMap.LightMapFragment.mapSize*2); + defer main.stackAllocator.free(_inflatedData); + const _inflatedLen = try utils.Compression.inflateTo(_inflatedData, data[9..]); + if(_inflatedLen != main.server.terrain.LightMap.LightMapFragment.mapSize*main.server.terrain.LightMap.LightMapFragment.mapSize*2) { + std.log.err("Transmission of light map has invalid size: {}. Input data: {any}, After inflate: {any}", .{_inflatedLen, data, _inflatedData[0.._inflatedLen]}); + } + data = _inflatedData; + const map = try main.globalAllocator.create(main.server.terrain.LightMap.LightMapFragment); + map.init(pos.wx, pos.wz, pos.voxelSize); + _ = map.refCount.fetchAdd(1, .Monotonic); + for(&map.startHeight) |*val| { + val.* = std.mem.readInt(i16, data[0..2], .big); + data = data[2..]; + } + try renderer.RenderStructure.updateLightMap(map); + } + pub fn sendLightMap(conn: *Connection, map: *main.server.terrain.LightMap.LightMapFragment) Allocator.Error!void { + var uncompressedData: [@sizeOf(@TypeOf(map.startHeight))]u8 = undefined; // TODO: #15280 + for(&map.startHeight, 0..) |val, i| { + std.mem.writeInt(i16, uncompressedData[2*i..][0..2], val, .big); + } + const compressedData = try utils.Compression.deflate(main.stackAllocator, &uncompressedData); + defer main.stackAllocator.free(compressedData); + const data = try main.stackAllocator.alloc(u8, 9 + compressedData.len); + defer main.stackAllocator.free(data); + @memcpy(data[9..], compressedData); + std.mem.writeInt(i32, data[0..4], map.pos.wx, .big); + std.mem.writeInt(i32, data[4..8], map.pos.wz, .big); + std.mem.writeInt(u8, data[8..9], map.pos.voxelSizeShift, .big); + try conn.sendImportant(id, data); + } + }; }; diff --git a/src/renderer.zig b/src/renderer.zig index 61ac537d..cd9cd34f 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -19,6 +19,7 @@ const settings = @import("settings.zig"); const utils = @import("utils.zig"); const vec = @import("vec.zig"); const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring; +const LightMap = main.server.terrain.LightMap; const Vec2f = vec.Vec2f; const Vec3i = vec.Vec3i; const Vec3f = vec.Vec3f; @@ -872,9 +873,11 @@ pub const RenderStructure = struct { const storageSize = 32; const storageMask = storageSize - 1; var storageLists: [settings.highestLOD + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined; + var mapStorageLists: [settings.highestLOD + 1]*[storageSize*storageSize]Atomic(?*LightMap.LightMapFragment) = undefined; var meshList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); var priorityMeshUpdateList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); pub var updatableList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); + var mapUpdatableList = std.ArrayList(*LightMap.LightMapFragment).init(main.globalAllocator); var clearList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); var lastPx: i32 = 0; var lastPy: i32 = 0; @@ -900,6 +903,10 @@ pub const RenderStructure = struct { val.rendered = false; } } + for(&mapStorageLists) |*mapStorageList| { + mapStorageList.* = try main.globalAllocator.create([storageSize*storageSize]Atomic(?*LightMap.LightMapFragment)); + @memset(mapStorageList.*, Atomic(?*LightMap.LightMapFragment).init(null)); + } } pub fn deinit() void { @@ -917,10 +924,17 @@ pub const RenderStructure = struct { for(storageLists) |storageList| { main.globalAllocator.destroy(storageList); } + for(mapStorageLists) |mapStorageList| { + main.globalAllocator.destroy(mapStorageList); + } for(updatableList.items) |mesh| { mesh.decreaseRefCount(); } updatableList.deinit(); + for(mapUpdatableList.items) |map| { + map.decreaseRefCount(); + } + mapUpdatableList.deinit(); for(priorityMeshUpdateList.items) |mesh| { mesh.decreaseRefCount(); } @@ -946,6 +960,26 @@ pub const RenderStructure = struct { return &storageLists[lod][@intCast(index)]; } + fn getMapPieceLocation(x: i32, z: i32, voxelSize: u31) *Atomic(?*LightMap.LightMapFragment) { + const lod = std.math.log2_int(u31, voxelSize); + var xIndex = x >> lod+LightMap.LightMapFragment.mapShift; + var zIndex = z >> lod+LightMap.LightMapFragment.mapShift; + xIndex &= storageMask; + zIndex &= storageMask; + const index = xIndex*storageSize + zIndex; + return &(&mapStorageLists)[lod][@intCast(index)]; + } + + pub fn getLightMapPieceAndIncreaseRefCount(x: i32, z: i32, voxelSize: u31) ?*LightMap.LightMapFragment { + const result: *LightMap.LightMapFragment = getMapPieceLocation(x, z, voxelSize).load(.Acquire) orelse return null; + var refCount: u16 = 1; + while(result.refCount.cmpxchgWeak(refCount, refCount+1, .Monotonic, .Monotonic)) |otherVal| { + if(otherVal == 0) return null; + refCount = otherVal; + } + return result; + } + fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block { const node = RenderStructure.getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=1}); const mesh = node.mesh.load(.Acquire) orelse return null; @@ -1049,6 +1083,28 @@ pub const RenderStructure = struct { return true; } + fn isMapInRenderDistance(pos: LightMap.MapFragmentPosition) bool { + const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize; + const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize)*pos.voxelSize; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + const minX = lastPx-%maxRenderDistance & invMask; + const maxX = lastPx+%maxRenderDistance+%size & invMask; + if(pos.wx < minX) return false; + if(pos.wx >= maxX) return false; + var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx); + deltaX = @max(0, deltaX - size/2); + + const maxZRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX); + if(maxZRenderDistance == 0) return false; + const minZ = lastPz-%maxZRenderDistance & invMask; + const maxZ = lastPz+%maxZRenderDistance+%size & invMask; + if(pos.wz < minZ) return false; + if(pos.wz >= maxZ) return false; + return true; + } + fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32) !void { for(0..storageLists.len) |_lod| { const lod: u5 = @intCast(_lod); @@ -1121,9 +1177,67 @@ pub const RenderStructure = struct { } } } + for(0..mapStorageLists.len) |_lod| { + const lod: u5 = @intCast(_lod); + const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; + const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; + const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); + + const minX = olderPx-%maxRenderDistanceOld & invMask; + const maxX = olderPx+%maxRenderDistanceOld+%size & invMask; + var x = minX; + while(x != maxX): (x +%= size) { + const xIndex = @divExact(x, size) & storageMask; + var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); + deltaXNew = @max(0, deltaXNew - size/2); + var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); + deltaXOld = @max(0, deltaXOld - size/2); + var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); + if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; + var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); + if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; + + const minZOld = olderPz-%maxZRenderDistanceOld & invMask; + const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; + const minZNew = lastPz-%maxZRenderDistanceNew & invMask; + const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; + + var zValues: [storageSize]i32 = undefined; + var zValuesLen: usize = 0; + if(minZNew -% minZOld > 0) { + var z = minZOld; + while(z != minZNew and z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZOld -% maxZNew > 0) { + var z = minZOld +% @max(0, maxZNew -% minZOld); + while(z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = xIndex*storageSize + zIndex; + + const mapAtomic = &mapStorageLists[_lod][@intCast(index)]; + if(mapAtomic.load(.Acquire)) |map| { + mapAtomic.store(null, .Release); + map.decreaseRefCount(); + } + } + } + } } - fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !void { + fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition), mapRequests: *std.ArrayList(LightMap.MapFragmentPosition)) !void { for(0..storageLists.len) |_lod| { const lod: u5 = @intCast(_lod); const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; @@ -1194,6 +1308,63 @@ pub const RenderStructure = struct { } } } + for(0..mapStorageLists.len) |_lod| { + const lod: u5 = @intCast(_lod); + const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; + const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; + const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); + + const minX = lastPx-%maxRenderDistanceNew & invMask; + const maxX = lastPx+%maxRenderDistanceNew+%size & invMask; + var x = minX; + while(x != maxX): (x +%= size) { + const xIndex = @divExact(x, size) & storageMask; + var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); + deltaXNew = @max(0, deltaXNew - size/2); + var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); + deltaXOld = @max(0, deltaXOld - size/2); + var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); + if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; + var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); + if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; + + const minZOld = olderPz-%maxZRenderDistanceOld & invMask; + const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; + const minZNew = lastPz-%maxZRenderDistanceNew & invMask; + const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; + + var zValues: [storageSize]i32 = undefined; + var zValuesLen: usize = 0; + if(minZOld -% minZNew > 0) { + var z = minZNew; + while(z != minZOld and z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZNew -% maxZOld > 0) { + var z = minZNew +% @max(0, maxZOld -% minZNew); + while(z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = xIndex*storageSize + zIndex; + const pos = LightMap.MapFragmentPosition{.wx=x, .wz=z, .voxelSize=@as(u31, 1)<= targetTime) break; // Update at least one mesh. } + while(mapUpdatableList.popOrNull()) |map| { + if(!isMapInRenderDistance(map.pos)) { + map.decreaseRefCount(); + } else { + if(getMapPieceLocation(map.pos.wx, map.pos.wz, map.pos.voxelSize).swap(map, .AcqRel)) |old| { + old.decreaseRefCount(); + } + } + } while(updatableList.items.len != 0) { // TODO: Find a faster solution than going through the entire list every frame. var closestPriority: f32 = -std.math.floatMax(f32); @@ -1707,4 +1890,10 @@ pub const RenderStructure = struct { pub fn updateChunkMesh(mesh: *chunk.Chunk) !void { try MeshGenerationTask.schedule(mesh); } + + pub fn updateLightMap(map: *LightMap.LightMapFragment) !void { + mutex.lock(); + defer mutex.unlock(); + try mapUpdatableList.append(map); + } }; diff --git a/src/server/terrain/LightMap.zig b/src/server/terrain/LightMap.zig new file mode 100644 index 00000000..36731d54 --- /dev/null +++ b/src/server/terrain/LightMap.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +const Atomic = std.atomic.Value; +const Allocator = std.mem.Allocator; + +const main = @import("root"); +const Chunk = main.chunk.Chunk; +const ChunkPosition = main.chunk.ChunkPosition; +const Cache = main.utils.Cache; +const JsonElement = main.JsonElement; + +const terrain = @import("terrain.zig"); +const TerrainGenerationProfile = terrain.TerrainGenerationProfile; +pub const MapFragmentPosition = terrain.SurfaceMap.MapFragmentPosition; +const Biome = terrain.biomes.Biome; + +/// Generates and stores the light start position for each block column. +pub const LightMapFragment = struct { + pub const mapShift = 8; + pub const mapSize = 1 << mapShift; + pub const mapMask = mapSize - 1; + + startHeight: [mapSize*mapSize]i16 = undefined, + pos: MapFragmentPosition, + + refCount: Atomic(u16) = Atomic(u16).init(0), + + pub fn init(self: *LightMapFragment, wx: i32, wz: i32, voxelSize: u31) void { + self.* = .{ + .pos = MapFragmentPosition.init(wx, wz, voxelSize), + }; + } + + pub fn decreaseRefCount(self: *LightMapFragment) void { + if(@atomicRmw(u16, &self.refCount.raw, .Sub, 1, .Monotonic) == 1) { + main.globalAllocator.destroy(self); + } + } + + pub fn getHeight(self: *LightMapFragment, wx: i32, wz: i32) i32 { + const xIndex = wx>>self.pos.voxelSizeShift & mapMask; + const zIndex = wz>>self.pos.voxelSizeShift & mapMask; + return self.startHeight[@as(usize, @intCast(xIndex)) << mapShift | @as(usize, @intCast(zIndex))]; + } +}; + + +const cacheSize = 1 << 6; // Must be a power of 2! +const cacheMask = cacheSize - 1; +const associativity = 8; // ~100MiB MiB Cache size +var cache: Cache(LightMapFragment, cacheSize, associativity, LightMapFragment.decreaseRefCount) = .{}; + +fn cacheInit(pos: MapFragmentPosition) !*LightMapFragment { + const mapFragment = try main.globalAllocator.create(LightMapFragment); + mapFragment.init(pos.wx, pos.wz, pos.voxelSize); + const surfaceMap = try terrain.SurfaceMap.getOrGenerateFragment(pos.wx, pos.wz, pos.voxelSize); + defer surfaceMap.deinit(); + comptime std.debug.assert(LightMapFragment.mapSize == terrain.SurfaceMap.MapFragment.mapSize); + for(0..LightMapFragment.mapSize) |x| { + for(0..LightMapFragment.mapSize) |z| { + const baseHeight: i16 = std.math.lossyCast(i16, surfaceMap.heightMap[x][z]); + mapFragment.startHeight[x << LightMapFragment.mapShift | z] = @max(0, baseHeight +| 16); // Simple heuristic. TODO: Update this value once chunks get generated in the region. + } + } + _ = @atomicRmw(u16, &mapFragment.refCount.raw, .Add, 1, .Monotonic); + return mapFragment; +} + +pub fn deinit() void { + cache.clear(); +} + +/// Call decreaseRefCount on the result. +pub fn getOrGenerateFragment(wx: i32, wz: i32, voxelSize: u31) !*LightMapFragment { + const compare = MapFragmentPosition.init( + wx & ~@as(i32, LightMapFragment.mapMask*voxelSize | voxelSize-1), + wz & ~@as(i32, LightMapFragment.mapMask*voxelSize | voxelSize-1), + voxelSize + ); + const result = try cache.findOrCreate(compare, cacheInit); + std.debug.assert(@atomicRmw(u16, &result.refCount.raw, .Add, 1, .Monotonic) != 0); + return result; +} \ No newline at end of file diff --git a/src/server/terrain/SurfaceMap.zig b/src/server/terrain/SurfaceMap.zig index 8eea7742..5170b229 100644 --- a/src/server/terrain/SurfaceMap.zig +++ b/src/server/terrain/SurfaceMap.zig @@ -12,7 +12,7 @@ const terrain = @import("terrain.zig"); const TerrainGenerationProfile = terrain.TerrainGenerationProfile; const Biome = terrain.biomes.Biome; -const MapFragmentPosition = struct { +pub const MapFragmentPosition = struct { wx: i32, wz: i32, voxelSize: u31, @@ -30,12 +30,10 @@ const MapFragmentPosition = struct { } pub fn equals(self: MapFragmentPosition, other: anytype) bool { - if(@TypeOf(other) == ?*MapFragment) { - if(other) |ch| { - return self.wx == ch.pos.wx and self.wz == ch.pos.wz and self.voxelSize == ch.pos.voxelSize; - } - return false; - } else @compileError("Unsupported"); + if(other) |ch| { + return self.wx == ch.pos.wx and self.wz == ch.pos.wz and self.voxelSize == ch.pos.voxelSize; + } + return false; } pub fn hashCode(self: MapFragmentPosition) u32 { diff --git a/src/server/terrain/terrain.zig b/src/server/terrain/terrain.zig index b0a2c7fe..ebd30646 100644 --- a/src/server/terrain/terrain.zig +++ b/src/server/terrain/terrain.zig @@ -12,6 +12,8 @@ pub const ClimateMap = @import("ClimateMap.zig"); pub const SurfaceMap = @import("SurfaceMap.zig"); +pub const LightMap = @import("LightMap.zig"); + pub const CaveBiomeMap = @import("CaveBiomeMap.zig"); pub const CaveMap = @import("CaveMap.zig"); @@ -146,4 +148,5 @@ pub fn deinit() void { CaveMap.deinit(); ClimateMap.deinit(); SurfaceMap.deinit(); + LightMap.deinit(); } \ No newline at end of file diff --git a/src/server/world.zig b/src/server/world.zig index bbe1afcb..b59f02ff 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -103,6 +103,62 @@ const ChunkManager = struct { } }; + const LightMapLoadTask = struct { + pos: terrain.SurfaceMap.MapFragmentPosition, + creationTime: i64, + source: ?*User, + + const vtable = utils.ThreadPool.VTable{ + .getPriority = @ptrCast(&getPriority), + .isStillNeeded = @ptrCast(&isStillNeeded), + .run = @ptrCast(&run), + .clean = @ptrCast(&clean), + }; + + pub fn schedule(pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void { + const task = try main.globalAllocator.create(LightMapLoadTask); + task.* = LightMapLoadTask { + .pos = pos, + .creationTime = std.time.milliTimestamp(), + .source = source, + }; + try main.threadPool.addTask(task, &vtable); + } + + pub fn getPriority(self: *LightMapLoadTask) f32 { + if(self.source) |user| { + const pos = ChunkPosition{.wx = self.pos.wx, .wy = @intFromFloat(user.player.pos[1]), .wz = self.pos.wz, .voxelSize = self.pos.voxelSize}; + return pos.getPriority(user.player.pos) + 100000; + } else { + return std.math.floatMax(f32); + } + } + + pub fn isStillNeeded(self: *LightMapLoadTask) bool { + _ = self; // TODO: Do these tasks need to be culled? + return true; + } + + pub fn run(self: *LightMapLoadTask) Allocator.Error!void { + defer self.clean(); + const map = try terrain.LightMap.getOrGenerateFragment(self.pos.wx, self.pos.wz, self.pos.voxelSize); + defer map.decreaseRefCount(); + if(self.source) |source| { + try main.network.Protocols.lightMapTransmission.sendLightMap(source.conn, map); + } else { + server.mutex.lock(); + defer server.mutex.unlock(); + for(server.users.items) |user| { + try main.network.Protocols.lightMapTransmission.sendLightMap(user.conn, map); + } + } + } + + pub fn clean(self: *LightMapLoadTask) void { + main.globalAllocator.destroy(self); + } + }; + pub fn init(world: *ServerWorld, settings: JsonElement) !ChunkManager { const self = ChunkManager { .world = world, @@ -135,6 +191,11 @@ const ChunkManager = struct { self.terrainGenerationProfile.deinit(); } + pub fn queueLightMap(self: ChunkManager, pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void { + _ = self; + try LightMapLoadTask.schedule(pos, source); + } + pub fn queueChunk(self: ChunkManager, pos: ChunkPosition, source: ?*User) !void { _ = self; try ChunkLoadTask.schedule(pos, source); @@ -531,6 +592,10 @@ pub const ServerWorld = struct { try self.chunkManager.queueChunk(pos, source); } + pub fn queueLightMap(self: *ServerWorld, pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void { + try self.chunkManager.queueLightMap(pos, source); + } + pub fn seek() !void { // TODO: Remove this MetaChunk stuff. It wasn't really useful and made everything needlessly complicated. // // Care about the metaChunks: