From 19a9e9ebb3b40f79daf926d40e5e89f28c77e8d1 Mon Sep 17 00:00:00 2001 From: Sotr Date: Tue, 26 Mar 2019 17:03:26 +0800 Subject: [PATCH] Async lighting updates --- .../server/core/AkarinGlobalConfig.java | 5 + .../minecraft/server/AkarinAsyncLighting.java | 54 ++++++ src/main/java/net/minecraft/server/Chunk.java | 45 ++++- .../net/minecraft/server/ChunkSection.java | 8 +- .../java/net/minecraft/server/HeightMap.java | 159 ++++++++++++++++++ .../minecraft/server/PaperLightingQueue.java | 6 +- .../net/minecraft/server/WorldServer.java | 6 +- .../craftbukkit/scheduler/CraftScheduler.java | 2 +- 8 files changed, 267 insertions(+), 18 deletions(-) create mode 100644 src/main/java/net/minecraft/server/AkarinAsyncLighting.java create mode 100644 src/main/java/net/minecraft/server/HeightMap.java diff --git a/src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java b/src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java index c702d9ac1..69201c5d5 100644 --- a/src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java +++ b/src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java @@ -180,4 +180,9 @@ public class AkarinGlobalConfig { private static void blockbreakAnimationVisibleDistance() { blockbreakAnimationVisibleDistance = Math.sqrt(getDouble("alternative.block-break-animation-visible-distance", 32.00)); } + + public static boolean enableAsyncLighting = true; + private static void enableAsyncLighting() { + enableAsyncLighting = getBoolean("core.async-lighting.enable", enableAsyncLighting); + } } \ No newline at end of file diff --git a/src/main/java/net/minecraft/server/AkarinAsyncLighting.java b/src/main/java/net/minecraft/server/AkarinAsyncLighting.java new file mode 100644 index 000000000..dd8292724 --- /dev/null +++ b/src/main/java/net/minecraft/server/AkarinAsyncLighting.java @@ -0,0 +1,54 @@ +package net.minecraft.server; + +import java.util.Map; +import java.util.function.IntConsumer; + +import javax.annotation.concurrent.ThreadSafe; + +import lombok.AllArgsConstructor; + +@ThreadSafe +@AllArgsConstructor +public class AkarinAsyncLighting { + private final World world; + private final ChunkSection[] sections; + private final Map heightMap; + + public void getBrightness(EnumSkyBlock enumskyblock, BlockPosition blockposition, IntConsumer callback) { + this.getBrightness(enumskyblock, blockposition, this.world.o().g(), callback); + } + + public void getBrightness(EnumSkyBlock enumskyblock, BlockPosition blockposition, boolean canSeeSky, IntConsumer callback) { + int i = blockposition.getX() & 15; + int j = blockposition.getY(); + int k = blockposition.getZ() & 15; + int l = j >> 4; + + if (l >= 0 && l <= this.sections.length - 1) { + ChunkSection chunksection = this.sections[l]; + + if (chunksection == Chunk.a) + callback.accept(this.canHasLight(blockposition) ? enumskyblock.c : 0); + + switch (enumskyblock) { + case SKY: + callback.accept(canSeeSky ? chunksection.c(i, j & 15, k) : 0); + case BLOCK: + callback.accept(chunksection.d(i, j & 15, k)); + default: + callback.accept(enumskyblock.c); + } + } else { + boolean hasLight = (enumskyblock == EnumSkyBlock.SKY && canSeeSky) || enumskyblock == EnumSkyBlock.BLOCK; + callback.accept(hasLight ? enumskyblock.c : 0); + } + } + + public boolean canHasLight(BlockPosition blockposition) { + int i = blockposition.getX() & 15; + int j = blockposition.getY(); + int k = blockposition.getZ() & 15; + + return j >= ((HeightMap) this.heightMap.get(HeightMap.Type.LIGHT_BLOCKING)).a(i, k); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 932a2d83b..85887f1ec 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -11,6 +11,8 @@ import com.destroystokyo.paper.exception.ServerInternalException; import com.google.common.collect.Maps; import com.google.common.collect.Queues; import com.google.common.collect.Sets; + +import io.akarin.server.core.AkarinGlobalConfig; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.shorts.ShortList; @@ -40,9 +42,9 @@ public class Chunk implements IChunkAccess { private static final Logger d = LogManager.getLogger(); public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = Chunk.a; // Paper - OBFHELPER - private final ChunkSection[] sections; + private volatile ChunkSection[] sections; // Akarin - volatile private final BiomeBase[] f; - private final boolean[] g; + private final BitSet g; // Akarin private final Map h; private boolean i;public boolean isLoaded() { return i; } // Paper - OBFHELPER public final World world; @@ -51,7 +53,7 @@ public class Chunk implements IChunkAccess { private static final Logger logger = LogManager.getLogger(); // Paper public final int locX; public final int locZ; - private boolean l; + private volatile boolean l; // Akarin private final ChunkConverter m; public final Map tileEntities; public final List[] entitySlices; // Spigot @@ -63,7 +65,7 @@ public class Chunk implements IChunkAccess { private boolean u; private boolean v;public boolean hasEntities() { return v; } // Paper - OBFHELPER private long lastSaved; - private boolean x; public boolean isModified() { return x; } // Paper - OBFHELPER + private volatile boolean x; public boolean isModified() { return x; } // Paper - OBFHELPER // Akarin - volatile private int y; private long z; private int A; @@ -72,9 +74,10 @@ public class Chunk implements IChunkAccess { private int D; private final AtomicInteger E; private final ChunkCoordIntPair F; + private final AkarinAsyncLighting lightHandler; // Akarin // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking - private int neighbors = 0x1 << 12; + private volatile int neighbors = 0x1 << 12; // Akarin - volatile public long chunkKey; // Paper start public final co.aikar.util.Counter entityCounts = new co.aikar.util.Counter<>(); @@ -137,7 +140,7 @@ public class Chunk implements IChunkAccess { public Chunk(World world, int i, int j, BiomeBase[] abiomebase, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long k) { this.sections = new ChunkSection[16]; - this.g = new boolean[256]; + this.g = new BitSet(256); // Akarin this.h = Maps.newHashMap(); this.heightMap = Maps.newEnumMap(HeightMap.Type.class); this.tileEntities = new TileEntityHashMap(); // Paper @@ -176,6 +179,7 @@ public class Chunk implements IChunkAccess { // CraftBukkit start this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); this.chunkKey = ChunkCoordIntPair.a(this.locX, this.locZ); + this.lightHandler = new AkarinAsyncLighting(world, sections, heightMap); // Akarin } public org.bukkit.Chunk bukkitChunk; @@ -250,6 +254,7 @@ public class Chunk implements IChunkAccess { } public void initLighting() { + Runnable runnable = () -> { // Akarin int i = this.b(); this.y = Integer.MAX_VALUE; @@ -291,10 +296,19 @@ public class Chunk implements IChunkAccess { } this.x = true; + // Akarin start + }; + if (AkarinGlobalConfig.enableAsyncLighting) + MCUtil.scheduleAsyncTask(runnable); + else + runnable.run(); + // Akarin end } private void c(int i, int j) { - this.g[i + j * 16] = true; + synchronized (this) { // Akarin - synchronized + this.g.set(i + j * 16); + } // Akarin - synchronized this.l = true; } @@ -303,8 +317,15 @@ public class Chunk implements IChunkAccess { if (this.areNeighborsLoaded(1)) { // Paper for (int i = 0; i < 16; ++i) { for (int j = 0; j < 16; ++j) { - if (this.g[i + j * 16]) { - this.g[i + j * 16] = false; + // Akarin start + int index = i + j * 16; + boolean has; + synchronized (this) { + if (has = this.g.get(index)) + this.g.set(index); + } + if (has) { + // Akarin end int k = this.a(HeightMap.Type.LIGHT_BLOCKING, i, j); int l = this.locX * 16 + i; int i1 = this.locZ * 16 + j; @@ -1513,6 +1534,12 @@ public class Chunk implements IChunkAccess { // Paper start public void runOrQueueLightUpdate(Runnable runnable) { + // Akarin start + if (AkarinGlobalConfig.enableAsyncLighting) { + MCUtil.scheduleAsyncTask(runnable); + return; + } + // Akarin end if (this.world.paperConfig.queueLightUpdates) { lightingQueue.add(runnable); } else { diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java index 9c6844d44..883d87e93 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -95,19 +95,19 @@ public class ChunkSection { return this.yPos; } - public void a(int i, int j, int k, int l) { + public synchronized void a(int i, int j, int k, int l) { // Akarin - synchronized this.skyLight.a(i, j, k, l); } - public int c(int i, int j, int k) { + public synchronized int c(int i, int j, int k) { // Akarin - synchronized return this.skyLight.a(i, j, k); } - public void b(int i, int j, int k, int l) { + public synchronized void b(int i, int j, int k, int l) { // Akarin - synchronized this.emittedLight.a(i, j, k, l); } - public int d(int i, int j, int k) { + public synchronized int d(int i, int j, int k) { // Akarin - synchronized return this.emittedLight.a(i, j, k); } diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java new file mode 100644 index 000000000..275a4b65a --- /dev/null +++ b/src/main/java/net/minecraft/server/HeightMap.java @@ -0,0 +1,159 @@ +package net.minecraft.server; + +import com.google.common.collect.Maps; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +public class HeightMap { + + private final DataBits a = new DataBits(9, 256); + private final PredicateBlock b; + private final IChunkAccess c; + + public HeightMap(IChunkAccess ichunkaccess, HeightMap.Type heightmap_type) { + this.b = PredicateBlocks.a(PredicateBlocks.b(heightmap_type.a())); + this.c = ichunkaccess; + } + + public void a() { + int i = this.c.b() + 16; + BlockPosition.PooledBlockPosition blockposition_pooledblockposition = BlockPosition.PooledBlockPosition.r(); + Throwable throwable = null; + + try { + for (int j = 0; j < 16; ++j) { + for (int k = 0; k < 16; ++k) { + this.a(j, k, this.a(blockposition_pooledblockposition, j, k, this.b, i)); + } + } + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; + } finally { + if (blockposition_pooledblockposition != null) { + if (throwable != null) { + try { + blockposition_pooledblockposition.close(); + } catch (Throwable throwable2) { + throwable.addSuppressed(throwable2); + } + } else { + blockposition_pooledblockposition.close(); + } + } + + } + + } + + public boolean a(int i, int j, int k, @Nullable IBlockData iblockdata) { + int l = this.a(i, k); + + if (j <= l - 2) { + return false; + } else { + if (this.b.test(iblockdata, this.c, new BlockPosition(i, j, k))) { + if (j >= l) { + this.a(i, k, j + 1); + return true; + } + } else if (l - 1 == j) { + this.a(i, k, this.a((BlockPosition.MutableBlockPosition) null, i, k, this.b, j)); + return true; + } + + return false; + } + } + + private int a(@Nullable BlockPosition.MutableBlockPosition blockposition_mutableblockposition, int i, int j, PredicateBlock predicateblock, int k) { + if (blockposition_mutableblockposition == null) { + blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); + } + + for (int l = k - 1; l >= 0; --l) { + blockposition_mutableblockposition.c(i, l, j); + IBlockData iblockdata = this.c.getType(blockposition_mutableblockposition); + + if (predicateblock.test(iblockdata, this.c, blockposition_mutableblockposition)) { + return l + 1; + } + } + + return 0; + } + + public int a(int i, int j) { + return this.a(b(i, j)); + } + + private synchronized int a(int i) { // Akarin - synchronized + return this.a.a(i); + } + + private synchronized void a(int i, int j, int k) { // Akarin - synchronized + this.a.a(b(i, j), k); + } + + public void a(long[] along) { + System.arraycopy(along, 0, this.a.a(), 0, along.length); + } + + public synchronized long[] b() { // Akarin - synchronized + return this.a.a(); + } + + private static int b(int i, int j) { + return i + j * 16; + } + + public static enum Type { + + WORLD_SURFACE_WG("WORLD_SURFACE_WG", HeightMap.Use.WORLDGEN, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR)}), OCEAN_FLOOR_WG("OCEAN_FLOOR_WG", HeightMap.Use.WORLDGEN, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR), PredicateBlockLiquid.a()}), LIGHT_BLOCKING("LIGHT_BLOCKING", HeightMap.Use.LIVE_WORLD, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR), PredicateBlockLightTransmission.a()}), MOTION_BLOCKING("MOTION_BLOCKING", HeightMap.Use.LIVE_WORLD, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR), PredicateBlockNotSolidOrLiquid.a()}), MOTION_BLOCKING_NO_LEAVES("MOTION_BLOCKING_NO_LEAVES", HeightMap.Use.LIVE_WORLD, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR), PredicateBlockTag.a(TagsBlock.LEAVES), PredicateBlockNotSolidOrLiquid.a()}), OCEAN_FLOOR("OCEAN_FLOOR", HeightMap.Use.LIVE_WORLD, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR), PredicateBlockSolid.a()}), WORLD_SURFACE("WORLD_SURFACE", HeightMap.Use.LIVE_WORLD, new PredicateBlock[] { PredicateBlockType.a(Blocks.AIR)}); + + private final PredicateBlock[] h; + private final String i; + private final HeightMap.Use j; + private static final Map k = (Map) SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Akarin - fixes decompile error + HeightMap.Type[] aheightmap_type = values(); + int i = aheightmap_type.length; + + for (int j = 0; j < i; ++j) { + HeightMap.Type heightmap_type = aheightmap_type[j]; + + hashmap.put(heightmap_type.i, heightmap_type); + } + + }); + + private Type(String s, HeightMap.Use heightmap_use, PredicateBlock... apredicateblock) { + this.i = s; + this.h = apredicateblock; + this.j = heightmap_use; + } + + public PredicateBlock[] a() { + return this.h; + } + + public String b() { + return this.i; + } + + public HeightMap.Use c() { + return this.j; + } + + public static HeightMap.Type a(String s) { + return (HeightMap.Type) HeightMap.Type.k.get(s); + } + } + + public static enum Use { + + WORLDGEN, LIVE_WORLD; + + private Use() {} + } +} diff --git a/src/main/java/net/minecraft/server/PaperLightingQueue.java b/src/main/java/net/minecraft/server/PaperLightingQueue.java index 47313d93e..46b7cfa5a 100644 --- a/src/main/java/net/minecraft/server/PaperLightingQueue.java +++ b/src/main/java/net/minecraft/server/PaperLightingQueue.java @@ -1,6 +1,8 @@ package net.minecraft.server; import co.aikar.timings.Timing; +import io.akarin.server.core.AkarinGlobalConfig; + import com.destroystokyo.paper.PaperConfig; import it.unimi.dsi.fastutil.objects.ObjectCollection; @@ -19,7 +21,7 @@ class PaperLightingQueue { START: for (World world : MinecraftServer.getServer().getWorlds()) { - if (!world.paperConfig.queueLightUpdates) { + if (!world.paperConfig.queueLightUpdates || AkarinGlobalConfig.enableAsyncLighting) { // Akarin continue; } @@ -71,7 +73,7 @@ class PaperLightingQueue { * Flushes lighting updates to unload the chunk */ void processUnload() { - if (!chunk.world.paperConfig.queueLightUpdates) { + if (!chunk.world.paperConfig.queueLightUpdates || AkarinGlobalConfig.enableAsyncLighting) { // Akarin return; } processQueue(0, 0); // No timeout diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index c60d7ba33..48687f2dc 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -446,11 +446,13 @@ public class WorldServer extends World implements IAsyncTaskHandler { if (spigotConfig.randomLightUpdates && !this.players.isEmpty()) { // Spigot int i = this.random.nextInt(this.players.size()); EntityHuman entityhuman = (EntityHuman) this.players.get(i); + MCUtil.scheduleAsyncTask(() -> { // Akarin int j = MathHelper.floor(entityhuman.locX) + this.random.nextInt(11) - 5; int k = MathHelper.floor(entityhuman.locY) + this.random.nextInt(11) - 5; int l = MathHelper.floor(entityhuman.locZ) + this.random.nextInt(11) - 5; this.r(new BlockPosition(j, k, l)); + }); // Akarin } //this.methodProfiler.exit(); // Akarin - remove caller @@ -472,14 +474,14 @@ public class WorldServer extends World implements IAsyncTaskHandler { //this.methodProfiler.enter(* // Akarin - remove caller - for (Iterator iterator1 = this.manager.b(); iterator1.hasNext(); this.methodProfiler.exit()) { + for (Iterator iterator1 = this.manager.b(); iterator1.hasNext(); /*this.methodProfiler.exit()*/) { // Akarin - remove caller //this.methodProfiler.enter(* // Akarin - remove caller Chunk chunk = (Chunk) iterator1.next(); int j = chunk.locX * 16; int k = chunk.locZ * 16; //this.methodProfiler.exitEnter("checkNextLight"); // Akarin - remove caller - chunk.x(); + MCUtil.scheduleAsyncTask(chunk::x); // Akarin //this.methodProfiler.exitEnter("tickChunk"); // Akarin - remove caller chunk.d(false); if ( !chunk.areNeighborsLoaded( 1 ) ) continue; // Spigot diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 5cd4ce9bc..5c4ec9e6c 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -540,7 +540,7 @@ public class CraftScheduler implements BukkitScheduler { task.setNext(null); } this.head = lastTask; - if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(Unsafe); // Paper + if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTimingUnsafe(); // Paper } private boolean isReady(final int currentTick) {