mirror of
https://github.com/Minestom/Minestom.git
synced 2024-10-31 15:59:35 +01:00
Send Lighting Immediately (#131)
Fix lighting (cherry picked from commit 0494ee0b97c0ce28ffd6d744a494c65fa8308658)
This commit is contained in:
parent
12da0c7e4a
commit
8aedd5fc26
@ -72,6 +72,7 @@ public class Main {
|
|||||||
commandManager.register(new ConfigCommand());
|
commandManager.register(new ConfigCommand());
|
||||||
commandManager.register(new SidebarCommand());
|
commandManager.register(new SidebarCommand());
|
||||||
commandManager.register(new SetEntityType());
|
commandManager.register(new SetEntityType());
|
||||||
|
commandManager.register(new RelightCommand());
|
||||||
|
|
||||||
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED)));
|
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED)));
|
||||||
|
|
||||||
|
@ -166,8 +166,16 @@ public class PlayerInit {
|
|||||||
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
|
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
|
||||||
|
|
||||||
InstanceContainer instanceContainer = instanceManager.createInstanceContainer(DimensionType.OVERWORLD);
|
InstanceContainer instanceContainer = instanceManager.createInstanceContainer(DimensionType.OVERWORLD);
|
||||||
instanceContainer.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
|
instanceContainer.setGenerator(unit -> {
|
||||||
|
unit.modifier().fillHeight(0, 40, Block.STONE);
|
||||||
|
|
||||||
|
if (unit.absoluteStart().blockY() < 40 && unit.absoluteEnd().blockY() > 40) {
|
||||||
|
unit.modifier().setBlock(unit.absoluteStart().blockX(), 40, unit.absoluteStart().blockZ(), Block.TORCH);
|
||||||
|
}
|
||||||
|
});
|
||||||
instanceContainer.setChunkSupplier(LightingChunk::new);
|
instanceContainer.setChunkSupplier(LightingChunk::new);
|
||||||
|
instanceContainer.setTimeRate(0);
|
||||||
|
instanceContainer.setTime(18000);
|
||||||
|
|
||||||
// var i2 = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, null, NamespaceID.from("minestom:demo"));
|
// var i2 = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, null, NamespaceID.from("minestom:demo"));
|
||||||
// instanceManager.registerInstance(i2);
|
// instanceManager.registerInstance(i2);
|
||||||
@ -181,6 +189,8 @@ public class PlayerInit {
|
|||||||
// CompletableFuture.runAsync(() -> {
|
// CompletableFuture.runAsync(() -> {
|
||||||
// CompletableFuture.allOf(chunks.toArray(CompletableFuture[]::new)).join();
|
// CompletableFuture.allOf(chunks.toArray(CompletableFuture[]::new)).join();
|
||||||
// System.out.println("load end");
|
// System.out.println("load end");
|
||||||
|
// LightingChunk.relight(instanceContainer, instanceContainer.getChunks());
|
||||||
|
// System.out.println("light end");
|
||||||
// });
|
// });
|
||||||
|
|
||||||
inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.minestom.demo.commands;
|
||||||
|
|
||||||
|
import net.minestom.server.command.builder.Command;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.instance.LightingChunk;
|
||||||
|
|
||||||
|
public class RelightCommand extends Command {
|
||||||
|
public RelightCommand() {
|
||||||
|
super("relight");
|
||||||
|
setDefaultExecutor((source, args) -> {
|
||||||
|
if (source instanceof Player player) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
source.sendMessage("Relighting...");
|
||||||
|
LightingChunk.relight(player.getInstance(), player.getInstance().getChunks());
|
||||||
|
source.sendMessage("Relighted " + player.getInstance().getChunks().size() + " chunks in " + (System.currentTimeMillis() - start) + "ms");
|
||||||
|
player.getInstance().getChunks().forEach(chunk -> chunk.sendChunk(player));
|
||||||
|
source.sendMessage("Chunks Received");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -210,24 +210,7 @@ public class DynamicChunk extends Chunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull ChunkDataPacket createChunkPacket() {
|
private @NotNull ChunkDataPacket createChunkPacket() {
|
||||||
final NBTCompound heightmapsNBT;
|
final NBTCompound heightmapsNBT = computeHeightmap();
|
||||||
// TODO: don't hardcode heightmaps
|
|
||||||
// Heightmap
|
|
||||||
{
|
|
||||||
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
|
||||||
int[] motionBlocking = new int[16 * 16];
|
|
||||||
int[] worldSurface = new int[16 * 16];
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
motionBlocking[x + z * 16] = 0;
|
|
||||||
worldSurface[x + z * 16] = dimensionHeight - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
|
||||||
heightmapsNBT = NBT.Compound(Map.of(
|
|
||||||
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
|
|
||||||
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
|
|
||||||
}
|
|
||||||
// Data
|
// Data
|
||||||
|
|
||||||
final byte[] data;
|
final byte[] data;
|
||||||
@ -238,44 +221,35 @@ public class DynamicChunk extends Chunk {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this instanceof LightingChunk light) {
|
|
||||||
if (light.lightCache.isValid()) {
|
|
||||||
return new ChunkDataPacket(chunkX, chunkZ,
|
|
||||||
new ChunkData(heightmapsNBT, data, entries),
|
|
||||||
createLightData(true));
|
|
||||||
} else {
|
|
||||||
// System.out.println("Regenerating light for chunk " + chunkX + " " + chunkZ);
|
|
||||||
LightingChunk.updateAfterGeneration(light);
|
|
||||||
return new ChunkDataPacket(chunkX, chunkZ,
|
|
||||||
new ChunkData(heightmapsNBT, data, entries),
|
|
||||||
createEmptyLight());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ChunkDataPacket(chunkX, chunkZ,
|
return new ChunkDataPacket(chunkX, chunkZ,
|
||||||
new ChunkData(heightmapsNBT, data, entries),
|
new ChunkData(heightmapsNBT, data, entries),
|
||||||
createLightData(true)
|
createLightData()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected NBTCompound computeHeightmap() {
|
||||||
|
// TODO: don't hardcode heightmaps
|
||||||
|
// Heightmap
|
||||||
|
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
||||||
|
int[] motionBlocking = new int[16 * 16];
|
||||||
|
int[] worldSurface = new int[16 * 16];
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
motionBlocking[x + z * 16] = 0;
|
||||||
|
worldSurface[x + z * 16] = dimensionHeight - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
||||||
|
return NBT.Compound(Map.of(
|
||||||
|
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
|
||||||
|
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull UpdateLightPacket createLightPacket() {
|
@NotNull UpdateLightPacket createLightPacket() {
|
||||||
return new UpdateLightPacket(chunkX, chunkZ, createLightData(false));
|
return new UpdateLightPacket(chunkX, chunkZ, createLightData());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LightData createEmptyLight() {
|
protected LightData createLightData() {
|
||||||
BitSet skyMask = new BitSet();
|
|
||||||
BitSet blockMask = new BitSet();
|
|
||||||
BitSet emptySkyMask = new BitSet();
|
|
||||||
BitSet emptyBlockMask = new BitSet();
|
|
||||||
List<byte[]> skyLights = new ArrayList<>();
|
|
||||||
List<byte[]> blockLights = new ArrayList<>();
|
|
||||||
|
|
||||||
return new LightData(skyMask, blockMask,
|
|
||||||
emptySkyMask, emptyBlockMask,
|
|
||||||
skyLights, blockLights);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LightData createLightData(boolean sendLater) {
|
|
||||||
BitSet skyMask = new BitSet();
|
BitSet skyMask = new BitSet();
|
||||||
BitSet blockMask = new BitSet();
|
BitSet blockMask = new BitSet();
|
||||||
BitSet emptySkyMask = new BitSet();
|
BitSet emptySkyMask = new BitSet();
|
||||||
@ -346,7 +320,7 @@ public class DynamicChunk extends Chunk {
|
|||||||
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
||||||
0, 5};
|
0, 5};
|
||||||
|
|
||||||
private static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
|
static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
|
||||||
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||||
final char valuesPerLong = (char) (64 / bitsPerEntry);
|
final char valuesPerLong = (char) (64 / bitsPerEntry);
|
||||||
final int magicIndex = 3 * (valuesPerLong - 1);
|
final int magicIndex = 3 * (valuesPerLong - 1);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.ServerFlag;
|
import net.minestom.server.ServerFlag;
|
||||||
import net.minestom.server.collision.Shape;
|
import net.minestom.server.collision.Shape;
|
||||||
@ -13,21 +11,22 @@ import net.minestom.server.instance.block.BlockHandler;
|
|||||||
import net.minestom.server.instance.light.Light;
|
import net.minestom.server.instance.light.Light;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.server.CachedPacket;
|
import net.minestom.server.network.packet.server.CachedPacket;
|
||||||
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
|
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||||
import net.minestom.server.network.packet.server.play.data.LightData;
|
import net.minestom.server.network.packet.server.play.data.LightData;
|
||||||
import net.minestom.server.timer.ExecutionType;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.timer.Task;
|
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
|
||||||
import net.minestom.server.utils.NamespaceID;
|
import net.minestom.server.utils.NamespaceID;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import static net.minestom.server.instance.light.LightCompute.emptyContent;
|
import static net.minestom.server.instance.light.LightCompute.emptyContent;
|
||||||
|
|
||||||
@ -37,8 +36,9 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
|
|
||||||
private int[] heightmap;
|
private int[] heightmap;
|
||||||
final CachedPacket lightCache = new CachedPacket(this::createLightPacket);
|
final CachedPacket lightCache = new CachedPacket(this::createLightPacket);
|
||||||
boolean sendNeighbours = true;
|
|
||||||
boolean chunkLoaded = false;
|
boolean chunkLoaded = false;
|
||||||
|
private int highestBlock;
|
||||||
|
private boolean initialLightingSent = false;
|
||||||
|
|
||||||
enum LightType {
|
enum LightType {
|
||||||
SKY,
|
SKY,
|
||||||
@ -132,17 +132,32 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onLoad() {
|
protected void onLoad() {
|
||||||
// Prefetch the chunk packet so that lazy lighting is computed
|
|
||||||
chunkLoaded = true;
|
chunkLoaded = true;
|
||||||
updateAfterGeneration(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] calculateHeightMap() {
|
public boolean isLightingCalculated() {
|
||||||
|
return initialLightingSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NBTCompound computeHeightmap() {
|
||||||
|
// Heightmap
|
||||||
|
int[] heightmap = getHeightmap();
|
||||||
|
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
||||||
|
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
||||||
|
return NBT.Compound(Map.of(
|
||||||
|
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight)),
|
||||||
|
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazy compute heightmap
|
||||||
|
public int[] getHeightmap() {
|
||||||
if (this.heightmap != null) return this.heightmap;
|
if (this.heightmap != null) return this.heightmap;
|
||||||
var heightmap = new int[CHUNK_SIZE_X * CHUNK_SIZE_Z];
|
var heightmap = new int[CHUNK_SIZE_X * CHUNK_SIZE_Z];
|
||||||
|
|
||||||
int minY = instance.getDimensionType().getMinY();
|
int minY = instance.getDimensionType().getMinY();
|
||||||
int maxY = instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight();
|
int maxY = instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight();
|
||||||
|
highestBlock = minY;
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
for (int x = 0; x < CHUNK_SIZE_X; x++) {
|
for (int x = 0; x < CHUNK_SIZE_X; x++) {
|
||||||
@ -154,6 +169,7 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
height--;
|
height--;
|
||||||
}
|
}
|
||||||
heightmap[z << 4 | x] = (height + 1);
|
heightmap[z << 4 | x] = (height + 1);
|
||||||
|
if (height > highestBlock) highestBlock = height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +179,12 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected LightData createLightData(boolean sendLater) {
|
protected LightData createLightData() {
|
||||||
|
if (lightCache.isValid()) {
|
||||||
|
ServerPacket packet = lightCache.packet(ConnectionState.PLAY);
|
||||||
|
return ((UpdateLightPacket) packet).lightData();
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (lightCache) {
|
synchronized (lightCache) {
|
||||||
BitSet skyMask = new BitSet();
|
BitSet skyMask = new BitSet();
|
||||||
BitSet blockMask = new BitSet();
|
BitSet blockMask = new BitSet();
|
||||||
@ -172,20 +193,25 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
List<byte[]> skyLights = new ArrayList<>();
|
List<byte[]> skyLights = new ArrayList<>();
|
||||||
List<byte[]> blockLights = new ArrayList<>();
|
List<byte[]> blockLights = new ArrayList<>();
|
||||||
|
|
||||||
|
Set<Chunk> combined = new HashSet<>();
|
||||||
|
int chunkMin = instance.getDimensionType().getMinY();
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (Section section : sections) {
|
for (Section section : sections) {
|
||||||
boolean wasUpdatedBlock = false;
|
boolean wasUpdatedBlock = false;
|
||||||
boolean wasUpdatedSky = false;
|
boolean wasUpdatedSky = false;
|
||||||
|
|
||||||
if (section.blockLight().requiresUpdate()) {
|
if (section.blockLight().requiresUpdate()) {
|
||||||
relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.BLOCK);
|
var needsSend = relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.BLOCK);
|
||||||
|
combined.addAll(needsSend);
|
||||||
wasUpdatedBlock = true;
|
wasUpdatedBlock = true;
|
||||||
} else if (section.blockLight().requiresSend()) {
|
} else if (section.blockLight().requiresSend()) {
|
||||||
wasUpdatedBlock = true;
|
wasUpdatedBlock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.skyLight().requiresUpdate()) {
|
if (section.skyLight().requiresUpdate()) {
|
||||||
relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.SKY);
|
var needsSend = relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.SKY);
|
||||||
|
combined.addAll(needsSend);
|
||||||
wasUpdatedSky = true;
|
wasUpdatedSky = true;
|
||||||
} else if (section.skyLight().requiresSend()) {
|
} else if (section.skyLight().requiresSend()) {
|
||||||
wasUpdatedSky = true;
|
wasUpdatedSky = true;
|
||||||
@ -195,8 +221,9 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
|
|
||||||
final byte[] skyLight = section.skyLight().array();
|
final byte[] skyLight = section.skyLight().array();
|
||||||
final byte[] blockLight = section.blockLight().array();
|
final byte[] blockLight = section.blockLight().array();
|
||||||
|
final int sectionMaxY = index * 16 + chunkMin;
|
||||||
|
|
||||||
if ((wasUpdatedSky || sendLater) && this.instance.getDimensionType().isSkylightEnabled()) {
|
if ((wasUpdatedSky) && this.instance.getDimensionType().isSkylightEnabled() && sectionMaxY <= (highestBlock + 16)) {
|
||||||
if (skyLight.length != 0 && skyLight != emptyContent) {
|
if (skyLight.length != 0 && skyLight != emptyContent) {
|
||||||
skyLights.add(skyLight);
|
skyLights.add(skyLight);
|
||||||
skyMask.set(index);
|
skyMask.set(index);
|
||||||
@ -205,7 +232,7 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasUpdatedBlock || sendLater) {
|
if (wasUpdatedBlock) {
|
||||||
if (blockLight.length != 0 && blockLight != emptyContent) {
|
if (blockLight.length != 0 && blockLight != emptyContent) {
|
||||||
blockLights.add(blockLight);
|
blockLights.add(blockLight);
|
||||||
blockMask.set(index);
|
blockMask.set(index);
|
||||||
@ -215,10 +242,27 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendNeighbours) {
|
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> {
|
||||||
updateAfterGeneration(this);
|
for (Chunk chunk : combined) {
|
||||||
sendNeighbours = false;
|
if (chunk instanceof LightingChunk light) {
|
||||||
}
|
if (light.initialLightingSent) {
|
||||||
|
light.lightCache.invalidate();
|
||||||
|
light.chunkCache.invalidate();
|
||||||
|
|
||||||
|
// Compute Lighting. This will ensure lighting is computed even with no players
|
||||||
|
lightCache.body(ConnectionState.PLAY);
|
||||||
|
light.sendLighting();
|
||||||
|
|
||||||
|
light.sections.forEach(s -> {
|
||||||
|
s.blockLight().setRequiresSend(true);
|
||||||
|
s.skyLight().setRequiresSend(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialLightingSent = true;
|
||||||
|
});
|
||||||
|
|
||||||
return new LightData(skyMask, blockMask,
|
return new LightData(skyMask, blockMask,
|
||||||
emptySkyMask, emptyBlockMask,
|
emptySkyMask, emptyBlockMask,
|
||||||
@ -226,100 +270,23 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final LongSet queuedChunks = new LongOpenHashSet();
|
private static Set<Chunk> flushQueue(Instance instance, Set<Point> queue, LightType type, QueueType queueType) {
|
||||||
private static final List<LightingChunk> sendQueue = new ArrayList<>();
|
|
||||||
private static Task sendingTask = null;
|
|
||||||
private static final ReentrantLock lightLock = new ReentrantLock();
|
|
||||||
private static final ReentrantLock queueLock = new ReentrantLock();
|
|
||||||
|
|
||||||
static void updateAfterGeneration(LightingChunk chunk) {
|
|
||||||
for (int i = -1; i <= 1; i++) {
|
|
||||||
for (int j = -1; j <= 1; j++) {
|
|
||||||
Chunk neighborChunk = chunk.instance.getChunk(chunk.chunkX + i, chunk.chunkZ + j);
|
|
||||||
if (neighborChunk == null) continue;
|
|
||||||
|
|
||||||
if (neighborChunk instanceof LightingChunk lightingChunk) {
|
|
||||||
queueLock.lock();
|
|
||||||
|
|
||||||
if (queuedChunks.add(ChunkUtils.getChunkIndex(lightingChunk.chunkX, lightingChunk.chunkZ))) {
|
|
||||||
sendQueue.add(lightingChunk);
|
|
||||||
}
|
|
||||||
queueLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lightLock.lock();
|
|
||||||
if (sendingTask != null) {
|
|
||||||
lightLock.unlock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendingTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
|
||||||
queueLock.lock();
|
|
||||||
var copy = new ArrayList<>(sendQueue);
|
|
||||||
sendQueue.clear();
|
|
||||||
queuedChunks.clear();
|
|
||||||
queueLock.unlock();
|
|
||||||
|
|
||||||
// if (copy.size() != 0) {
|
|
||||||
// System.out.println("Sending lighting for " + copy.size() + " chunks");
|
|
||||||
// }
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (LightingChunk f : copy) {
|
|
||||||
f.sections.forEach(s -> {
|
|
||||||
s.blockLight().invalidate();
|
|
||||||
s.skyLight().invalidate();
|
|
||||||
});
|
|
||||||
f.chunkCache.invalidate();
|
|
||||||
f.lightCache.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all the lighting
|
|
||||||
for (LightingChunk f : copy) {
|
|
||||||
if (f.isLoaded()) {
|
|
||||||
f.lightCache.body(ConnectionState.PLAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send it slowly
|
|
||||||
for (LightingChunk f : copy) {
|
|
||||||
if (f.isLoaded()) {
|
|
||||||
f.sendLighting();
|
|
||||||
if (f.getViewers().size() == 0) continue;
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
|
|
||||||
if (count % ServerFlag.LIGHTING_CHUNKS_PER_SEND == 0) {
|
|
||||||
// System.out.println("Sent " + count + " lighting chunks " + (count * 100 / copy.size()) + "%");
|
|
||||||
try {
|
|
||||||
Thread.sleep(ServerFlag.LIGHTING_CHUNKS_SEND_DELAY);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, TaskSchedule.immediate(), TaskSchedule.tick(20), ExecutionType.ASYNC);
|
|
||||||
lightLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void flushQueue(Instance instance, Set<Point> queue, LightType type, QueueType queueType) {
|
|
||||||
AtomicInteger count = new AtomicInteger(0);
|
|
||||||
Set<Light> sections = ConcurrentHashMap.newKeySet();
|
Set<Light> sections = ConcurrentHashMap.newKeySet();
|
||||||
Set<Point> newQueue = ConcurrentHashMap.newKeySet();
|
Set<Point> newQueue = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
Set<Chunk> responseChunks = ConcurrentHashMap.newKeySet();
|
||||||
|
List<CompletableFuture<Void>> tasks = new ArrayList<>();
|
||||||
|
|
||||||
for (Point point : queue) {
|
for (Point point : queue) {
|
||||||
Chunk chunk = instance.getChunk(point.blockX(), point.blockZ());
|
Chunk chunk = instance.getChunk(point.blockX(), point.blockZ());
|
||||||
if (chunk == null) {
|
if (chunk == null) continue;
|
||||||
count.getAndIncrement();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var light = type == LightType.BLOCK ? chunk.getSection(point.blockY()).blockLight() : chunk.getSection(point.blockY()).skyLight();
|
var section = chunk.getSection(point.blockY());
|
||||||
|
responseChunks.add(chunk);
|
||||||
|
|
||||||
pool.submit(() -> {
|
var light = type == LightType.BLOCK ? section.blockLight() : section.skyLight();
|
||||||
|
|
||||||
|
CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
|
||||||
if (queueType == QueueType.INTERNAL) light.calculateInternal(instance, chunk.getChunkX(), point.blockY(), chunk.getChunkZ());
|
if (queueType == QueueType.INTERNAL) light.calculateInternal(instance, chunk.getChunkX(), point.blockY(), chunk.getChunkZ());
|
||||||
else light.calculateExternal(instance, chunk, point.blockY());
|
else light.calculateExternal(instance, chunk, point.blockY());
|
||||||
|
|
||||||
@ -327,16 +294,19 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
|
|
||||||
var toAdd = light.flip();
|
var toAdd = light.flip();
|
||||||
if (toAdd != null) newQueue.addAll(toAdd);
|
if (toAdd != null) newQueue.addAll(toAdd);
|
||||||
|
}, pool);
|
||||||
|
|
||||||
count.incrementAndGet();
|
tasks.add(task);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (count.get() < queue.size()) { }
|
tasks.forEach(CompletableFuture::join);
|
||||||
|
|
||||||
if (newQueue.size() > 0) {
|
if (!newQueue.isEmpty()) {
|
||||||
flushQueue(instance, newQueue, type, QueueType.EXTERNAL);
|
var newResponse = flushQueue(instance, newQueue, type, QueueType.EXTERNAL);
|
||||||
|
responseChunks.addAll(newResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return responseChunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void relight(Instance instance, Collection<Chunk> chunks) {
|
public static void relight(Instance instance, Collection<Chunk> chunks) {
|
||||||
@ -345,10 +315,12 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
for (Chunk chunk : chunks) {
|
for (Chunk chunk : chunks) {
|
||||||
if (chunk == null) continue;
|
if (chunk == null) continue;
|
||||||
for (int section = chunk.minSection; section < chunk.maxSection; section++) {
|
for (int section = chunk.minSection; section < chunk.maxSection; section++) {
|
||||||
chunk.getSection(section).blockLight().invalidate();
|
if (chunk instanceof LightingChunk) {
|
||||||
chunk.getSection(section).skyLight().invalidate();
|
chunk.getSection(section).blockLight().invalidate();
|
||||||
|
chunk.getSection(section).skyLight().invalidate();
|
||||||
|
|
||||||
sections.add(new Vec(chunk.getChunkX(), section, chunk.getChunkZ()));
|
sections.add(new Vec(chunk.getChunkX(), section, chunk.getChunkZ()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,10 +330,23 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<Point> getNearbyRequired(Instance instance, Point point) {
|
private static Set<Point> getNearbyRequired(Instance instance, Point point, LightType type) {
|
||||||
Set<Point> collected = new HashSet<>();
|
Set<Point> collected = new HashSet<>();
|
||||||
collected.add(point);
|
collected.add(point);
|
||||||
|
|
||||||
|
int highestRegionPoint = instance.getDimensionType().getMinY();
|
||||||
|
|
||||||
|
for (int x = point.blockX() - 1; x <= point.blockX() + 1; x++) {
|
||||||
|
for (int z = point.blockZ() - 1; z <= point.blockZ() + 1; z++) {
|
||||||
|
Chunk chunkCheck = instance.getChunk(x, z);
|
||||||
|
if (chunkCheck == null) continue;
|
||||||
|
|
||||||
|
if (chunkCheck instanceof LightingChunk lighting) {
|
||||||
|
if (lighting.highestBlock > highestRegionPoint) highestRegionPoint = lighting.highestBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int x = point.blockX() - 1; x <= point.blockX() + 1; x++) {
|
for (int x = point.blockX() - 1; x <= point.blockX() + 1; x++) {
|
||||||
for (int z = point.blockZ() - 1; z <= point.blockZ() + 1; z++) {
|
for (int z = point.blockZ() - 1; z <= point.blockZ() + 1; z++) {
|
||||||
Chunk chunkCheck = instance.getChunk(x, z);
|
Chunk chunkCheck = instance.getChunk(x, z);
|
||||||
@ -369,6 +354,8 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
|
|
||||||
for (int y = point.blockY() - 1; y <= point.blockY() + 1; y++) {
|
for (int y = point.blockY() - 1; y <= point.blockY() + 1; y++) {
|
||||||
Point sectionPosition = new Vec(x, y, z);
|
Point sectionPosition = new Vec(x, y, z);
|
||||||
|
int sectionHeight = instance.getDimensionType().getMinY() + 16 * y;
|
||||||
|
if ((sectionHeight + 16) > highestRegionPoint && type == LightType.SKY) continue;
|
||||||
|
|
||||||
if (sectionPosition.blockY() < chunkCheck.getMaxSection() && sectionPosition.blockY() >= chunkCheck.getMinSection()) {
|
if (sectionPosition.blockY() < chunkCheck.getMaxSection() && sectionPosition.blockY() >= chunkCheck.getMinSection()) {
|
||||||
Section s = chunkCheck.getSection(sectionPosition.blockY());
|
Section s = chunkCheck.getSection(sectionPosition.blockY());
|
||||||
@ -383,16 +370,16 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
return collected;
|
return collected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<Point> collectRequiredNearby(Instance instance, Point point) {
|
private static Set<Point> collectRequiredNearby(Instance instance, Point point, LightType type) {
|
||||||
final Set<Point> found = new HashSet<>();
|
final Set<Point> found = new HashSet<>();
|
||||||
final ArrayDeque<Point> toCheck = new ArrayDeque<>();
|
final ArrayDeque<Point> toCheck = new ArrayDeque<>();
|
||||||
|
|
||||||
toCheck.add(point);
|
toCheck.add(point);
|
||||||
found.add(point);
|
found.add(point);
|
||||||
|
|
||||||
while (toCheck.size() > 0) {
|
while (!toCheck.isEmpty()) {
|
||||||
final Point current = toCheck.poll();
|
final Point current = toCheck.poll();
|
||||||
final Set<Point> nearby = getNearbyRequired(instance, current);
|
final Set<Point> nearby = getNearbyRequired(instance, current, type);
|
||||||
nearby.forEach(p -> {
|
nearby.forEach(p -> {
|
||||||
if (!found.contains(p)) {
|
if (!found.contains(p)) {
|
||||||
found.add(p);
|
found.add(p);
|
||||||
@ -404,25 +391,24 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void relightSection(Instance instance, int chunkX, int sectionY, int chunkZ) {
|
static Set<Chunk> relightSection(Instance instance, int chunkX, int sectionY, int chunkZ) {
|
||||||
relightSection(instance, chunkX, sectionY, chunkZ, LightType.BLOCK);
|
var res = new HashSet<>(relightSection(instance, chunkX, sectionY, chunkZ, LightType.BLOCK));
|
||||||
relightSection(instance, chunkX, sectionY, chunkZ, LightType.SKY);
|
res.addAll(relightSection(instance, chunkX, sectionY, chunkZ, LightType.SKY));
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void relightSection(Instance instance, int chunkX, int sectionY, int chunkZ, LightType type) {
|
private static Set<Chunk> relightSection(Instance instance, int chunkX, int sectionY, int chunkZ, LightType type) {
|
||||||
Chunk c = instance.getChunk(chunkX, chunkZ);
|
Chunk c = instance.getChunk(chunkX, chunkZ);
|
||||||
if (c == null) return;
|
if (c == null) return Set.of();
|
||||||
|
|
||||||
synchronized (instance) {
|
synchronized (instance) {
|
||||||
Set<Point> collected = collectRequiredNearby(instance, new Vec(chunkX, sectionY, chunkZ));
|
Set<Point> collected = collectRequiredNearby(instance, new Vec(chunkX, sectionY, chunkZ), type);
|
||||||
// System.out.println("Calculating " + chunkX + " " + sectionY + " " + chunkZ + " | " + collected.size() + " | " + type);
|
return relight(instance, collected, type);
|
||||||
|
|
||||||
relight(instance, collected, type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void relight(Instance instance, Set<Point> queue, LightType type) {
|
private static Set<Chunk> relight(Instance instance, Set<Point> queue, LightType type) {
|
||||||
flushQueue(instance, queue, type, QueueType.INTERNAL);
|
return flushQueue(instance, queue, type, QueueType.INTERNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +26,7 @@ final class BlockLight implements Light {
|
|||||||
private byte[] contentPropagation;
|
private byte[] contentPropagation;
|
||||||
private byte[] contentPropagationSwap;
|
private byte[] contentPropagationSwap;
|
||||||
|
|
||||||
private boolean isValidBorders = true;
|
private boolean isValidBorders = false;
|
||||||
private boolean needsSend = true;
|
private boolean needsSend = true;
|
||||||
|
|
||||||
private Set<Point> toUpdateSet = new HashSet<>();
|
private Set<Point> toUpdateSet = new HashSet<>();
|
||||||
@ -214,6 +214,11 @@ final class BlockLight implements Light {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiresSend(boolean b) {
|
||||||
|
this.needsSend = b;
|
||||||
|
}
|
||||||
|
|
||||||
private void clearCache() {
|
private void clearCache() {
|
||||||
this.contentPropagation = null;
|
this.contentPropagation = null;
|
||||||
isValidBorders = true;
|
isValidBorders = true;
|
||||||
|
@ -27,6 +27,7 @@ public interface Light {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean requiresSend();
|
boolean requiresSend();
|
||||||
|
void setRequiresSend(boolean b);
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
byte[] array();
|
byte[] array();
|
||||||
|
@ -14,9 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static net.minestom.server.instance.light.LightCompute.*;
|
import static net.minestom.server.instance.light.LightCompute.*;
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ final class SkyLight implements Light {
|
|||||||
private byte[] contentPropagation;
|
private byte[] contentPropagation;
|
||||||
private byte[] contentPropagationSwap;
|
private byte[] contentPropagationSwap;
|
||||||
|
|
||||||
private boolean isValidBorders = true;
|
private boolean isValidBorders = false;
|
||||||
private boolean needsSend = true;
|
private boolean needsSend = true;
|
||||||
|
|
||||||
private Set<Point> toUpdateSet = new HashSet<>();
|
private Set<Point> toUpdateSet = new HashSet<>();
|
||||||
@ -59,7 +57,7 @@ final class SkyLight implements Light {
|
|||||||
ShortArrayFIFOQueue lightSources = new ShortArrayFIFOQueue();
|
ShortArrayFIFOQueue lightSources = new ShortArrayFIFOQueue();
|
||||||
|
|
||||||
if (c instanceof LightingChunk lc) {
|
if (c instanceof LightingChunk lc) {
|
||||||
int[] heightmap = lc.calculateHeightMap();
|
int[] heightmap = lc.getHeightmap();
|
||||||
int maxY = c.getInstance().getDimensionType().getMinY() + c.getInstance().getDimensionType().getHeight();
|
int maxY = c.getInstance().getDimensionType().getMinY() + c.getInstance().getDimensionType().getHeight();
|
||||||
int sectionMaxY = (sectionY + 1) * 16 - 1;
|
int sectionMaxY = (sectionY + 1) * 16 - 1;
|
||||||
int sectionMinY = sectionY * 16;
|
int sectionMinY = sectionY * 16;
|
||||||
@ -240,6 +238,11 @@ final class SkyLight implements Light {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiresSend(boolean b) {
|
||||||
|
this.needsSend = b;
|
||||||
|
}
|
||||||
|
|
||||||
private void clearCache() {
|
private void clearCache() {
|
||||||
this.contentPropagation = null;
|
this.contentPropagation = null;
|
||||||
isValidBorders = true;
|
isValidBorders = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user