Add sunlight.

This commit is contained in:
IntegratedQuantum 2023-12-20 14:25:56 +01:00
parent dcb76a77a8
commit 3eb3e2b358
8 changed files with 459 additions and 12 deletions

View File

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

View File

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

View File

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

View File

@ -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)<<lod, .voxelSizeShift = lod};
const node = &mapStorageLists[_lod][@intCast(index)];
std.debug.assert(node.load(.Acquire) == null);
try mapRequests.append(pos);
}
}
}
}
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32) ![]*chunk.meshing.ChunkMesh {
@ -1204,6 +1375,8 @@ pub const RenderStructure = struct {
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.globalAllocator);
defer meshRequests.deinit();
var mapRequests = std.ArrayList(LightMap.MapFragmentPosition).init(main.globalAllocator);
defer mapRequests.deinit();
const olderPx = lastPx;
const olderPy = lastPy;
@ -1217,9 +1390,10 @@ pub const RenderStructure = struct {
mutex.unlock();
try freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
try createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests);
try createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests, &mapRequests);
// Make requests as soon as possible to reduce latency:
try network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items);
try network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items);
// Does occlusion using a breadth-first search that caches an on-screen visibility rectangle.
@ -1559,6 +1733,15 @@ pub const RenderStructure = struct {
try mesh.uploadData();
if(std.time.milliTimestamp() >= 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);
}
};

View File

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

View File

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

View File

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

View File

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