Sponge Async Lighting Patch v2 w/ Mixin version fixes
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
package io.akarin.api;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public interface IMixinChunk {
|
||||
AtomicInteger getPendingLightUpdates();
|
||||
|
||||
long getLightUpdateTime();
|
||||
}
|
||||
28
sources/src/main/java/io/akarin/api/mixin/IMixinChunk.java
Normal file
28
sources/src/main/java/io/akarin/api/mixin/IMixinChunk.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package io.akarin.api.mixin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
|
||||
public interface IMixinChunk {
|
||||
AtomicInteger getPendingLightUpdates();
|
||||
|
||||
long getLightUpdateTime();
|
||||
|
||||
boolean areNeighborsLoaded();
|
||||
|
||||
@Nullable Chunk getNeighborChunk(int index);
|
||||
|
||||
CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type);
|
||||
|
||||
List<Chunk> getNeighbors();
|
||||
|
||||
void setNeighborChunk(int index, @Nullable Chunk chunk);
|
||||
|
||||
void setLightUpdateTime(long time);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.akarin.api.mixin;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
|
||||
public interface IMixinWorldServer {
|
||||
boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk chunk);
|
||||
|
||||
ExecutorService getLightingExecutor();
|
||||
}
|
||||
127
sources/src/main/java/io/akarin/server/mixin/cps/MixinChunk.java
Normal file
127
sources/src/main/java/io/akarin/server/mixin/cps/MixinChunk.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.cps;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
private Chunk[] neighborChunks = new Chunk[4];
|
||||
private static final EnumDirection[] CARDINAL_DIRECTIONS = new EnumDirection[] {EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.EAST, EnumDirection.WEST};
|
||||
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
|
||||
@Override
|
||||
public Chunk getNeighborChunk(int index) {
|
||||
return this.neighborChunks[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeighborChunk(int index, @Nullable Chunk chunk) {
|
||||
this.neighborChunks[index] = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Chunk> getNeighbors() {
|
||||
List<Chunk> neighborList = Lists.newArrayList();
|
||||
for (Chunk neighbor : this.neighborChunks) {
|
||||
if (neighbor != null) {
|
||||
neighborList.add(neighbor);
|
||||
}
|
||||
}
|
||||
return neighborList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areNeighborsLoaded() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (this.neighborChunks[i] == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int directionToIndex(EnumDirection direction) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
return 0;
|
||||
case SOUTH:
|
||||
return 1;
|
||||
case EAST:
|
||||
return 2;
|
||||
case WEST:
|
||||
return 3;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected direction");
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "addEntities", at = @At("RETURN"))
|
||||
public void onLoadReturn(CallbackInfo ci) {
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), locX, locZ);
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, neighbor);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, (Chunk) (Object) this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "removeEntities", at = @At("RETURN"))
|
||||
public void onUnload(CallbackInfo ci) {
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), locX, locZ);
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, null);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import io.akarin.api.mixin.IMixinWorldServer;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Blocks;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.ChunkSection;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.TileEntity;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.BlockPosition.MutableBlockPosition;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false, priority = 1001)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
|
||||
// Keeps track of block positions in this chunk currently queued for sky light update
|
||||
private CopyOnWriteArrayList<Short> queuedSkyLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
// Keeps track of block positions in this chunk currently queued for block light update
|
||||
private CopyOnWriteArrayList<Short> queuedBlockLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
private AtomicInteger pendingLightUpdates = new AtomicInteger();
|
||||
private long lightUpdateTime;
|
||||
private ExecutorService lightExecutorService;
|
||||
|
||||
@Shadow private boolean m; // PAIL: isGapLightingUpdated
|
||||
@Shadow private boolean r; // PAIL: ticked
|
||||
@Shadow @Final private ChunkSection[] sections;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int[] heightMap;
|
||||
/** Which columns need their skylightMaps updated. */
|
||||
@Shadow @Final private boolean[] i; // PAIL: updateSkylightColumns
|
||||
/** Queue containing the BlockPosition of tile entities queued for creation */
|
||||
@Shadow @Final private ConcurrentLinkedQueue<BlockPosition> y; // PAIL: tileEntityPosQueue
|
||||
/** Boolean value indicating if the terrain is populated. */
|
||||
@Shadow private boolean done; // isTerrainPopulated
|
||||
@Shadow(aliases = "lit") private boolean isLightPopulated;
|
||||
/** Lowest value in the heightmap. */
|
||||
@Shadow private int v; // PAIL: heightMapMinimum
|
||||
|
||||
@Shadow public abstract int b(int x, int z); // PAIL: getHeightValue
|
||||
@Shadow @Nullable public abstract TileEntity g(BlockPosition pos); // PAIL: createNewTileEntity
|
||||
@Shadow @Nullable public abstract TileEntity a(BlockPosition pos, Chunk.EnumTileEntityState state); // PAIL: getTileEntity
|
||||
@Shadow @Final public abstract IBlockData getBlockData(BlockPosition pos);
|
||||
@Shadow @Final public abstract IBlockData getBlockData(int x, int y, int z);
|
||||
@Shadow public abstract boolean isUnloading();
|
||||
/** Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary */
|
||||
@Shadow public abstract void b(int x, int z, int maxValue); // PAIL: checkSkylightNeighborHeight
|
||||
@Shadow public abstract void a(int x, int z, int startY, int endY); // PAIL: updateSkylightNeighborHeight
|
||||
@Shadow public abstract void z(); // PAIL: setSkylightUpdated
|
||||
@Shadow public abstract int g(); // PAIL: getTopFilledSegment
|
||||
@Shadow public abstract void markDirty();
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
public void onConstruct(World worldIn, int x, int z, CallbackInfo ci) {
|
||||
this.lightExecutorService = ((IMixinWorldServer) worldIn).getLightingExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtomicInteger getPendingLightUpdates() {
|
||||
return this.pendingLightUpdates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLightUpdateTime() {
|
||||
return this.lightUpdateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightUpdateTime(long time) {
|
||||
this.lightUpdateTime = time;
|
||||
}
|
||||
|
||||
@Inject(method = "b(Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onTickHead(boolean skipRecheckGaps, CallbackInfo ci) {
|
||||
final List<Chunk> neighbors = this.getSurroundingChunks();
|
||||
if (this.m && this.world.worldProvider.m() && !skipRecheckGaps && !neighbors.isEmpty()) { // PAIL: isGapLightingUpdated - hasSkyLight
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.recheckGapsAsync(neighbors);
|
||||
});
|
||||
this.m = false; // PAIL: isGapLightingUpdated
|
||||
}
|
||||
|
||||
this.r = true; // PAIL: ticked
|
||||
|
||||
if (!this.isLightPopulated && this.done && !neighbors.isEmpty()) {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighbors);
|
||||
});
|
||||
// set to true to avoid requeuing the same task when not finished
|
||||
this.isLightPopulated = true;
|
||||
}
|
||||
|
||||
while (!this.y.isEmpty()) { // PAIL: tileEntityPosQueue
|
||||
BlockPosition blockpos = this.y.poll(); // PAIL: tileEntityPosQueue
|
||||
|
||||
if (this.a(blockpos, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockpos).getBlock().isTileEntity()) { // PAIL: getTileEntity
|
||||
TileEntity tileentity = this.g(blockpos); // PAIL: createNewTileEntity
|
||||
this.world.setTileEntity(blockpos, tileentity);
|
||||
this.world.b(blockpos, blockpos); // PAIL: markBlockRangeForRenderUpdate
|
||||
}
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Redirect(method = "b(III)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getHighestBlockYAt(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/BlockPosition;"))
|
||||
private BlockPosition onCheckSkylightGetHeight(World world, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return BlockPosition.ZERO;
|
||||
}
|
||||
|
||||
return new BlockPosition(pos.getX(), chunk.b(pos.getX() & 15, pos.getZ() & 15), pos.getZ()); // PAIL: getHeightValue
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.areChunksLoaded(Lnet/minecraft/server/BlockPosition;I)Z"))
|
||||
private boolean onAreaLoadedSkyLightNeighbor(World world, BlockPosition pos, int radius) {
|
||||
return this.isAreaLoaded();
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.c(Lnet/minecraft/server/EnumSkyBlock;Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightForSkylightNeighbor(World world, EnumSkyBlock enumSkyBlock, BlockPosition pos) {
|
||||
return this.checkWorldLightFor(enumSkyBlock, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechecks chunk gaps async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void recheckGapsAsync(List<Chunk> neighbors) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (this.i[i + j * 16]) { // PAIL: updateSkylightColumns
|
||||
this.i[i + j * 16] = false; // PAIL: updateSkylightColumns
|
||||
int k = this.b(i, j); // PAIL: getHeightValue
|
||||
int l = this.locX * 16 + i;
|
||||
int i1 = this.locZ * 16 + j;
|
||||
int j1 = Integer.MAX_VALUE;
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
final Chunk chunk = this.getLightChunk((l + enumfacing.getAdjacentX()) >> 4, (i1 + enumfacing.getAdjacentZ()) >> 4, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
continue;
|
||||
}
|
||||
j1 = Math.min(j1, chunk.w()); // PAIL: getLowestHeight
|
||||
}
|
||||
|
||||
this.b(l, i1, j1); // PAIL: checkSkylightNeighborHeight
|
||||
|
||||
for (EnumDirection enumfacing1 : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.b(l + enumfacing1.getAdjacentX(), i1 + enumfacing1.getAdjacentZ(), k); // PAIL: checkSkylightNeighborHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this.m = false; // PAIL: isGapLightingUpdated
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getType(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/IBlockData;"))
|
||||
private IBlockData onRelightChecksGetBlockData(World world, BlockPosition pos) {
|
||||
Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), pos.getX() >> 4, pos.getZ() >> 4);
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) chunk;
|
||||
if (chunk == null || chunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return Blocks.AIR.getBlockData();
|
||||
}
|
||||
|
||||
return chunk.getBlockData(pos);
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onRelightChecksCheckLight(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
// Avoids grabbing chunk async during light check
|
||||
@Redirect(method = "e(II)Z", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightWorld(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
@Inject(method = "o()V", at = @At("HEAD"), cancellable = true)
|
||||
private void checkLightHead(CallbackInfo ci) {
|
||||
if (this.world.getMinecraftServer().isStopped() || this.lightExecutorService.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUnloading()) {
|
||||
return;
|
||||
}
|
||||
final List<Chunk> neighborChunks = this.getSurroundingChunks();
|
||||
if (neighborChunks.isEmpty()) {
|
||||
this.isLightPopulated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
try {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
});
|
||||
} catch (RejectedExecutionException e) {
|
||||
// This could happen if ServerHangWatchdog kills the server
|
||||
// between the start of the method and the execute() call.
|
||||
if (!this.world.getMinecraftServer().isStopped() && !this.lightExecutorService.isShutdown()) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void checkLightAsync(List<Chunk> neighbors) {
|
||||
this.done = true;
|
||||
this.isLightPopulated = true;
|
||||
BlockPosition blockpos = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
label44:
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (!this.checkLightAsync(i, j, neighbors)) {
|
||||
this.isLightPopulated = false;
|
||||
break label44;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isLightPopulated) {
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
int k = enumfacing.c() == EnumDirection.EnumAxisDirection.POSITIVE ? 16 : 1; // PAIL: getAxisDirection
|
||||
final BlockPosition pos = blockpos.shift(enumfacing, k);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
chunk.a(enumfacing.opposite()); // PAIL: checkLightSide
|
||||
}
|
||||
|
||||
this.z(); // PAIL: setSkylightUpdated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param x The x position of chunk
|
||||
* @param z The z position of chunk
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkLightAsync(int x, int z, List<Chunk> neighbors) {
|
||||
int i = this.g(); // PAIL: getTopFilledSegment
|
||||
boolean flag = false;
|
||||
boolean flag1 = false;
|
||||
MutableBlockPosition blockpos$mutableblockpos = new MutableBlockPosition((this.locX << 4) + x, 0, (this.locZ << 4) + z);
|
||||
|
||||
for (int j = i + 16 - 1; j > this.world.getSeaLevel() || j > 0 && !flag1; --j) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), j, blockpos$mutableblockpos.getZ());
|
||||
int k = this.getBlockData(blockpos$mutableblockpos).c(); // PAIL: getLightOpacity
|
||||
|
||||
if (k == 255 && blockpos$mutableblockpos.getY() < this.world.getSeaLevel()) {
|
||||
flag1 = true;
|
||||
}
|
||||
|
||||
if (!flag && k > 0) {
|
||||
flag = true;
|
||||
} else if (flag && k == 0 && !this.checkWorldLight(blockpos$mutableblockpos, neighbors)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int l = blockpos$mutableblockpos.getY(); l > 0; --l) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), l, blockpos$mutableblockpos.getZ());
|
||||
|
||||
if (this.getBlockData(blockpos$mutableblockpos).d() > 0) { // getLightValue
|
||||
this.checkWorldLight(blockpos$mutableblockpos, neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe method to retrieve a chunk during async light updates.
|
||||
*
|
||||
* @param chunkX The x position of chunk.
|
||||
* @param chunkZ The z position of chunk.
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return The chunk if available, null if not
|
||||
*/
|
||||
private Chunk getLightChunk(int chunkX, int chunkZ, List<Chunk> neighbors) {
|
||||
final Chunk currentChunk = (Chunk) (Object) this;
|
||||
if (currentChunk.a(chunkX, chunkZ)) { // PAIL: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
if (neighbors == null) {
|
||||
neighbors = this.getSurroundingChunks();
|
||||
if (neighbors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(chunkX, chunkZ)) { // PAIL: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if surrounding chunks are loaded thread-safe.
|
||||
*
|
||||
* @return True if surrounded chunks are loaded, false if not
|
||||
*/
|
||||
private boolean isAreaLoaded() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets surrounding chunks thread-safe.
|
||||
*
|
||||
* @return The list of surrounding chunks, empty list if not loaded
|
||||
*/
|
||||
private List<Chunk> getSurroundingChunks() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Chunk> chunkList = Lists.newArrayList();
|
||||
chunkList = this.getNeighbors();
|
||||
chunkList.add(southEastChunk);
|
||||
chunkList.add(southWestChunk);
|
||||
chunkList.add(northEastChunk);
|
||||
chunkList.add(northWestChunk);
|
||||
return chunkList;
|
||||
}
|
||||
|
||||
@Inject(method = "c(III)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onRelightBlock(int x, int y, int z, CallbackInfo ci) {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.relightBlockAsync(x, y, z);
|
||||
});
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relight's a block async.
|
||||
*
|
||||
* @param x The x position
|
||||
* @param y The y position
|
||||
* @param z The z position
|
||||
*/
|
||||
private void relightBlockAsync(int x, int y, int z) {
|
||||
int i = this.heightMap[z << 4 | x] & 255;
|
||||
int j = i;
|
||||
|
||||
if (y > i) {
|
||||
j = y;
|
||||
}
|
||||
|
||||
while (j > 0 && this.getBlockData(x, j - 1, z).c() == 0) {
|
||||
--j;
|
||||
}
|
||||
|
||||
if (j != i) {
|
||||
this.markBlocksDirtyVerticalAsync(x + this.locX * 16, z + this.locZ * 16, j, i);
|
||||
this.heightMap[z << 4 | x] = j;
|
||||
int k = this.locX * 16 + x;
|
||||
int l = this.locZ * 16 + z;
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
if (j < i) {
|
||||
for (int j1 = j; j1 < i; ++j1) {
|
||||
ChunkSection extendedblockstorage2 = this.sections[j1 >> 4];
|
||||
|
||||
if (extendedblockstorage2 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage2.a(x, j1 & 15, z, 15); // PAIL: setSkyLight
|
||||
this.world.m(new BlockPosition((this.locX << 4) + x, j1, (this.locZ << 4) + z)); // PAIL: notifyLightSet
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i1 = i; i1 < j; ++i1) {
|
||||
ChunkSection extendedblockstorage = this.sections[i1 >> 4];
|
||||
|
||||
if (extendedblockstorage != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage.a(x, i1 & 15, z, 0); // PAIL: setSkyLight
|
||||
this.world.m(new BlockPosition((this.locX << 4) + x, i1, (this.locZ << 4) + z)); // PAIL: notifyLightSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int k1 = 15;
|
||||
|
||||
while (j > 0 && k1 > 0) {
|
||||
--j;
|
||||
int i2 = this.getBlockData(x, j, z).c();
|
||||
|
||||
if (i2 == 0) {
|
||||
i2 = 1;
|
||||
}
|
||||
|
||||
k1 -= i2;
|
||||
|
||||
if (k1 < 0) {
|
||||
k1 = 0;
|
||||
}
|
||||
|
||||
ChunkSection extendedblockstorage1 = this.sections[j >> 4];
|
||||
|
||||
if (extendedblockstorage1 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage1.a(x, j & 15, z, k1); // PAIL: setSkyLight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int l1 = this.heightMap[z << 4 | x];
|
||||
int j2 = i;
|
||||
int k2 = l1;
|
||||
|
||||
if (l1 < i) {
|
||||
j2 = l1;
|
||||
k2 = i;
|
||||
}
|
||||
|
||||
if (l1 < this.v) { // PAIL: heightMapMinimum
|
||||
this.v = l1; // PAIL: heightMapMinimum
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.a(k + enumfacing.getAdjacentX(), l + enumfacing.getAdjacentZ(), j2, k2); // PAIL: updateSkylightNeighborHeight
|
||||
}
|
||||
|
||||
this.a(k, l, j2, k2); // PAIL: updateSkylightNeighborHeight
|
||||
}
|
||||
|
||||
this.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a vertical line of blocks as dirty async.
|
||||
* Instead of calling world directly, we pass chunk safely for async light method.
|
||||
*
|
||||
* @param x1
|
||||
* @param z1
|
||||
* @param x2
|
||||
* @param z2
|
||||
*/
|
||||
private void markBlocksDirtyVerticalAsync(int x1, int z1, int x2, int z2) {
|
||||
if (x2 > z2) {
|
||||
int i = z2;
|
||||
z2 = x2;
|
||||
x2 = i;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
for (int j = x2; j <= z2; ++j) {
|
||||
final BlockPosition pos = new BlockPosition(x1, j, z1);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, new BlockPosition(x1, j, z1), chunk);
|
||||
}
|
||||
}
|
||||
|
||||
this.world.b(x1, x2, z1, x1, z2, z1); // PAIL: markBlockRangeForRenderUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light thread-safe.
|
||||
*
|
||||
* @param lightType The type of light to check
|
||||
* @param pos The block position
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLightFor(EnumSkyBlock lightType, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((IMixinWorldServer) this.world).updateLightAsync(lightType, pos, chunk);
|
||||
}
|
||||
|
||||
private boolean checkWorldLight(BlockPosition pos) {
|
||||
return this.checkWorldLight(pos, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light async.
|
||||
*
|
||||
* @param pos The block position
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLight(BlockPosition pos, List<Chunk> neighbors) {
|
||||
boolean flag = false;
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
flag |= ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, pos, chunk);
|
||||
}
|
||||
|
||||
flag = flag | ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.BLOCK, pos, chunk);
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of block positions currently queued for lighting updates.
|
||||
*
|
||||
* @param type The light type
|
||||
* @return The list of queued block positions, empty if none
|
||||
*/
|
||||
@Override
|
||||
public CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type) {
|
||||
if (type == EnumSkyBlock.SKY) {
|
||||
return this.queuedSkyLightingUpdates;
|
||||
}
|
||||
return this.queuedBlockLightingUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,27 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -6,7 +30,7 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.IMixinChunk;
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import net.minecraft.server.ChunkProviderServer;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IChunkProvider;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = World.class, remap = false)
|
||||
public abstract class MixinWorld {
|
||||
@Shadow protected IChunkProvider chunkProvider;
|
||||
@Shadow int[] J; // PAIL: lightUpdateBlockList
|
||||
|
||||
@Shadow public abstract boolean c(EnumSkyBlock lightType, BlockPosition pos); // PAIL: checkLightFor
|
||||
@Shadow public abstract MinecraftServer getMinecraftServer();
|
||||
@Shadow public abstract boolean areChunksLoaded(BlockPosition center, int radius, boolean allowEmpty);
|
||||
@Shadow public abstract void m(BlockPosition pos); // PAIL: notifyLightSet
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import io.akarin.api.mixin.IMixinWorldServer;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.WorldServer;
|
||||
import net.minecraft.server.BlockPosition.PooledBlockPosition;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false)
|
||||
public abstract class MixinWorldServer extends MixinWorld implements IMixinWorldServer {
|
||||
|
||||
private static final int NUM_XZ_BITS = 4;
|
||||
private static final int NUM_SHORT_Y_BITS = 8;
|
||||
private static final short XZ_MASK = 0xF;
|
||||
private static final short Y_SHORT_MASK = 0xFF;
|
||||
|
||||
private final ExecutorService lightExecutorService = Executors.newFixedThreadPool(1, new ThreadFactoryBuilder().setNameFormat("Akarin Async Light Thread").build());
|
||||
|
||||
@Override
|
||||
public boolean c(EnumSkyBlock lightType, BlockPosition pos) { // PAIL: checkLightFor
|
||||
return updateLightAsync(lightType, pos, null);
|
||||
}
|
||||
|
||||
public boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
// Sponge - This check is not needed as neighbors are checked in updateLightAsync
|
||||
if (false && !this.areChunksLoaded(pos, 17, false)) {
|
||||
return false;
|
||||
} else {
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = this.getLightForAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int l = this.getRawBlockLightAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int i1 = pos.getX();
|
||||
int j1 = pos.getY();
|
||||
int k1 = pos.getZ();
|
||||
|
||||
if (l > k) {
|
||||
this.J[j++] = 133152;
|
||||
} else if (l < k) {
|
||||
this.J[j++] = 133152 | k << 18;
|
||||
|
||||
while (i < j) {
|
||||
int l1 = this.J[i++];
|
||||
int i2 = (l1 & 63) - 32 + i1;
|
||||
int j2 = (l1 >> 6 & 63) - 32 + j1;
|
||||
int k2 = (l1 >> 12 & 63) - 32 + k1;
|
||||
int l2 = l1 >> 18 & 15;
|
||||
BlockPosition blockpos = new BlockPosition(i2, j2, k2);
|
||||
int i3 = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (i3 == l2) {
|
||||
this.setLightForAsync(lightType, blockpos, 0, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (l2 > 0) {
|
||||
int j3 = Math.abs(i2 - i1); // TODO MathHelper
|
||||
int k3 = Math.abs(j2 - j1);
|
||||
int l3 = Math.abs(k2 - k1);
|
||||
|
||||
if (j3 + k3 + l3 < 17) {
|
||||
PooledBlockPosition blockpos$pooledmutableblockpos = PooledBlockPosition.aquire();
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.values()) {
|
||||
int i4 = i2 + enumfacing.getAdjacentX();
|
||||
int j4 = j2 + enumfacing.getAdjacentX();
|
||||
int k4 = k2 + enumfacing.getAdjacentX();
|
||||
blockpos$pooledmutableblockpos.setValues(i4, j4, k4);
|
||||
// Sponge start - get chunk safely
|
||||
final Chunk pooledChunk = this.getLightChunk(blockpos$pooledmutableblockpos, currentChunk, neighbors);
|
||||
if (pooledChunk == null) {
|
||||
continue;
|
||||
}
|
||||
int l4 = Math.max(1, pooledChunk.getBlockData(blockpos$pooledmutableblockpos).c()); // PAIL: getLightOpacity
|
||||
i3 = this.getLightForAsync(lightType, blockpos$pooledmutableblockpos, currentChunk, neighbors);
|
||||
// Sponge end
|
||||
|
||||
if (i3 == l2 - l4 && j < this.J.length) {
|
||||
this.J[j++] = i4 - i1 + 32 | j4 - j1 + 32 << 6 | k4 - k1 + 32 << 12 | l2 - l4 << 18;
|
||||
}
|
||||
}
|
||||
|
||||
blockpos$pooledmutableblockpos.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
}
|
||||
|
||||
while (i < j) {
|
||||
int i5 = this.J[i++];
|
||||
int j5 = (i5 & 63) - 32 + i1;
|
||||
int k5 = (i5 >> 6 & 63) - 32 + j1;
|
||||
int l5 = (i5 >> 12 & 63) - 32 + k1;
|
||||
BlockPosition blockpos1 = new BlockPosition(j5, k5, l5);
|
||||
int i6 = this.getLightForAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int j6 = this.getRawBlockLightAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 != i6) {
|
||||
this.setLightForAsync(lightType, blockpos1, j6, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 > i6) {
|
||||
int k6 = Math.abs(j5 - i1);
|
||||
int l6 = Math.abs(k5 - j1);
|
||||
int i7 = Math.abs(l5 - k1);
|
||||
boolean flag = j < this.J.length - 6;
|
||||
|
||||
if (k6 + l6 + i7 < 17 && flag) {
|
||||
// Sponge start - use thread safe method getLightForAsync
|
||||
if (this.getLightForAsync(lightType, blockpos1.west(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.east(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 + 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.down(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.up(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 + 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.north(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - 1 - k1 + 32 << 12);
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.south(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 + 1 - k1 + 32 << 12);
|
||||
}
|
||||
// Sponge end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sponge start - Asynchronous light updates
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).remove((Short) this.blockPosToShort(pos));
|
||||
spongeChunk.getPendingLightUpdates().decrementAndGet();
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().decrementAndGet();
|
||||
}
|
||||
|
||||
// Sponge end
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, @Nullable Chunk currentChunk) {
|
||||
if (this.getMinecraftServer().isStopped() || this.lightExecutorService.isShutdown()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentChunk == null) {
|
||||
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
|
||||
}
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
if (currentChunk == null || currentChunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final short shortPos = this.blockPosToShort(pos);
|
||||
if (spongeChunk.getQueuedLightingUpdates(lightType).contains(shortPos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk chunk = currentChunk;
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).add(shortPos);
|
||||
spongeChunk.getPendingLightUpdates().incrementAndGet();
|
||||
spongeChunk.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
|
||||
List<Chunk> neighbors = spongeChunk.getNeighbors();
|
||||
// add diagonal chunks
|
||||
Chunk southEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
Chunk southWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
Chunk northEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
Chunk northWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (southEastChunk != null) {
|
||||
neighbors.add(southEastChunk);
|
||||
}
|
||||
if (southWestChunk != null) {
|
||||
neighbors.add(southWestChunk);
|
||||
}
|
||||
if (northEastChunk != null) {
|
||||
neighbors.add(northEastChunk);
|
||||
}
|
||||
if (northWestChunk != null) {
|
||||
neighbors.add(northWestChunk);
|
||||
}
|
||||
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().incrementAndGet();
|
||||
neighbor.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
}
|
||||
|
||||
//System.out.println("size = " + ((ThreadPoolExecutor) this.lightExecutorService).getQueue().size());
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
});
|
||||
} else {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ExecutorService getLightingExecutor() {
|
||||
return this.lightExecutorService;
|
||||
}
|
||||
|
||||
// Thread safe methods to retrieve a chunk during async light updates
|
||||
// Each method avoids calling getLoadedChunk and instead accesses the passed neighbor chunk list to avoid concurrency issues
|
||||
public Chunk getLightChunk(BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (currentChunk.a(pos.getX() >> 4, pos.getZ() >> 4)) { // PAIL: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(pos.getX() >> 4, pos.getZ() >> 4)) { // PAIL: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getLightForAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.getY() < 0) {
|
||||
pos = new BlockPosition(pos.getX(), 0, pos.getZ());
|
||||
}
|
||||
if (!(pos.isValidLocation())) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
|
||||
return chunk.getBrightness(lightType, pos);
|
||||
}
|
||||
|
||||
private int getRawBlockLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
final Chunk chunk = getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
if (lightType == EnumSkyBlock.SKY && chunk.c(pos)) { // PAIL: canSeeSky
|
||||
return 15;
|
||||
} else {
|
||||
IBlockData blockState = chunk.getBlockData(pos);
|
||||
int blockLight = blockState.d(); // getLightValue
|
||||
int i = lightType == EnumSkyBlock.SKY ? 0 : blockLight;
|
||||
int j = blockState.c(); // PAIL: getLightOpacity
|
||||
|
||||
if (j >= 15 && blockLight > 0) {
|
||||
j = 1;
|
||||
}
|
||||
|
||||
if (j < 1) {
|
||||
j = 1;
|
||||
}
|
||||
|
||||
if (j >= 15) {
|
||||
return 0;
|
||||
} else if (i >= 14) {
|
||||
return i;
|
||||
} else {
|
||||
for (EnumDirection facing : EnumDirection.values()) {
|
||||
BlockPosition blockpos = pos.shift(facing);
|
||||
int k = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors) - j;
|
||||
|
||||
if (k > i) {
|
||||
i = k;
|
||||
}
|
||||
|
||||
if (i >= 14) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightForAsync(EnumSkyBlock type, BlockPosition pos, int lightValue, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.isValidLocation()) {
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk != null && !chunk.isUnloading()) {
|
||||
chunk.a(type, pos, lightValue); // PAIL: setBrightness
|
||||
this.m(pos); // PAIL: notifyLightSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private short blockPosToShort(BlockPosition pos) {
|
||||
short serialized = (short) setNibble(0, pos.getX() & XZ_MASK, 0, NUM_XZ_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getY() & Y_SHORT_MASK, 1, NUM_SHORT_Y_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getZ() & XZ_MASK, 3, NUM_XZ_BITS);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies bits in an integer.
|
||||
*
|
||||
* @param num Integer to modify
|
||||
* @param data Bits of data to add
|
||||
* @param which Index of nibble to start at
|
||||
* @param bitsToReplace The number of bits to replace starting from nibble index
|
||||
* @return The modified integer
|
||||
*/
|
||||
private int setNibble(int num, int data, int which, int bitsToReplace) {
|
||||
return (num & ~(bitsToReplace << (which * 4)) | (data << (which * 4)));
|
||||
}
|
||||
}
|
||||
1444
sources/src/main/java/net/minecraft/server/Chunk.java
Normal file
1444
sources/src/main/java/net/minecraft/server/Chunk.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "1",
|
||||
"minVersion": "0.7.8",
|
||||
"package": "io.akarin.server.mixin",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
@@ -21,6 +21,7 @@
|
||||
"core.MixinMinecraftServer",
|
||||
"core.MixinChunkIOExecutor",
|
||||
|
||||
"cps.MixinChunk",
|
||||
"cps.MixinCraftWorld",
|
||||
"cps.MixinChunkProviderServer",
|
||||
|
||||
@@ -28,6 +29,11 @@
|
||||
"nsc.OptimisticNetworkManager",
|
||||
"nsc.NonblockingServerConnection",
|
||||
|
||||
"lighting.MixinChunk",
|
||||
"lighting.MixinWorld",
|
||||
"lighting.MixinWorldServer",
|
||||
"lighting.MixinChunkProviderServer",
|
||||
|
||||
"optimization.WeakBigTree",
|
||||
"optimization.WeakEnchantmentManager",
|
||||
"optimization.MixinEntityHorseAbstract",
|
||||
|
||||
Submodule work/Paper updated: 78469842d7...88d8c2594e
Reference in New Issue
Block a user