From b1cba01d8a1076be8f83077de542d6bc32acd587 Mon Sep 17 00:00:00 2001 From: Poweruser_rs Date: Thu, 17 Dec 2015 05:00:58 +0100 Subject: [PATCH] Configurable async light updates diff --git a/src/main/java/net/frozenorb/LightingUpdater.java b/src/main/java/net/frozenorb/LightingUpdater.java new file mode 100644 index 000000000..a0b5960e3 --- /dev/null +++ b/src/main/java/net/frozenorb/LightingUpdater.java @@ -0,0 +1,244 @@ +package net.frozenorb; + +import java.util.HashMap; +import java.util.List; + +import org.bukkit.craftbukkit.util.LongHash; + +import net.minecraft.server.Block; +import net.minecraft.server.Blocks; +import net.minecraft.server.Chunk; +import net.minecraft.server.EnumSkyBlock; +import net.minecraft.server.Facing; +import net.minecraft.server.IWorldAccess; +import net.minecraft.server.MathHelper; + +public class LightingUpdater { + + private int[] arrayI; + private HashMap chunks; + + public LightingUpdater() { + this.arrayI = new int['\u8000']; + this.chunks = new HashMap(); + } + + public boolean c(EnumSkyBlock enumskyblock, int i, int j, int k, Chunk chunk, List neighbors) { // PaperSpigot + if (chunk != null && neighbors != null) { + this.chunks.clear(); + this.chunks.put(LongHash.toLong(chunk.locX, chunk.locZ), chunk); + for(Chunk neighborchunk: neighbors) { + this.chunks.put(LongHash.toLong(neighborchunk.locX, neighborchunk.locZ), neighborchunk); + } + + int l = 0; + int i1 = 0; + + //this.methodProfiler.a("getBrightness"); + int j1 = this.b(enumskyblock, i, j, k); + int k1 = this.a(i, j, k, enumskyblock); + int l1; + int i2; + int j2; + int k2; + int l2; + int i3; + int j3; + int k3; + int l3; + + if (k1 > j1) { + arrayI[i1++] = 133152; + } else if (k1 < j1) { + arrayI[i1++] = 133152 | j1 << 18; + + while (l < i1) { + l1 = arrayI[l++]; + i2 = (l1 & 63) - 32 + i; + j2 = (l1 >> 6 & 63) - 32 + j; + k2 = (l1 >> 12 & 63) - 32 + k; + l2 = l1 >> 18 & 15; + i3 = this.b(enumskyblock, i2, j2, k2); + if (i3 == l2) { + this.b(enumskyblock, i2, j2, k2, 0); + if (l2 > 0) { + j3 = MathHelper.a(i2 - i); + l3 = MathHelper.a(j2 - j); + k3 = MathHelper.a(k2 - k); + if (j3 + l3 + k3 < 17) { + for (int i4 = 0; i4 < 6; ++i4) { + int j4 = i2 + Facing.b[i4]; + int k4 = j2 + Facing.c[i4]; + int l4 = k2 + Facing.d[i4]; + int i5 = Math.max(1, this.getType(j4, k4, l4).k()); + + i3 = this.b(enumskyblock, j4, k4, l4); + if (i3 == l2 - i5 && i1 < arrayI.length) { + arrayI[i1++] = j4 - i + 32 | k4 - j + 32 << 6 | l4 - k + 32 << 12 | l2 - i5 << 18; + } + } + } + } + } + } + + l = 0; + } + + //this.methodProfiler.b(); + //this.methodProfiler.a("checkedPosition < toCheckCount"); + + while (l < i1) { + l1 = arrayI[l++]; + i2 = (l1 & 63) - 32 + i; + j2 = (l1 >> 6 & 63) - 32 + j; + k2 = (l1 >> 12 & 63) - 32 + k; + l2 = this.b(enumskyblock, i2, j2, k2); + i3 = this.a(i2, j2, k2, enumskyblock); + if (i3 != l2) { + this.b(enumskyblock, i2, j2, k2, i3); + if (i3 > l2) { + j3 = Math.abs(i2 - i); + l3 = Math.abs(j2 - j); + k3 = Math.abs(k2 - k); + boolean flag = i1 < arrayI.length - 6; + + if (j3 + l3 + k3 < 17 && flag) { + if (this.b(enumskyblock, i2 - 1, j2, k2) < i3) { + arrayI[i1++] = i2 - 1 - i + 32 + (j2 - j + 32 << 6) + (k2 - k + 32 << 12); + } + + if (this.b(enumskyblock, i2 + 1, j2, k2) < i3) { + arrayI[i1++] = i2 + 1 - i + 32 + (j2 - j + 32 << 6) + (k2 - k + 32 << 12); + } + + if (this.b(enumskyblock, i2, j2 - 1, k2) < i3) { + arrayI[i1++] = i2 - i + 32 + (j2 - 1 - j + 32 << 6) + (k2 - k + 32 << 12); + } + + if (this.b(enumskyblock, i2, j2 + 1, k2) < i3) { + arrayI[i1++] = i2 - i + 32 + (j2 + 1 - j + 32 << 6) + (k2 - k + 32 << 12); + } + + if (this.b(enumskyblock, i2, j2, k2 - 1) < i3) { + arrayI[i1++] = i2 - i + 32 + (j2 - j + 32 << 6) + (k2 - 1 - k + 32 << 12); + } + + if (this.b(enumskyblock, i2, j2, k2 + 1) < i3) { + arrayI[i1++] = i2 - i + 32 + (j2 - j + 32 << 6) + (k2 + 1 - k + 32 << 12); + } + } + } + } + } + + // PaperSpigot start - Asynchronous light updates + if (chunk.world.paperSpigotConfig.useAsyncLighting) { + chunk.pendingLightUpdates.decrementAndGet(); + if (neighbors != null) { + for (Chunk neighbor : neighbors) { + neighbor.pendingLightUpdates.decrementAndGet(); + } + } + } + // PaperSpigot end + //this.methodProfiler.b(); + this.chunks.clear(); + return true; + } + return false; + } + + private void b(EnumSkyBlock enumskyblock, int i, int j, int k, int l) { + if (i >= -30000000 && k >= -30000000 && i < 30000000 && k < 30000000) { + if (j >= 0) { + if (j < 256) { + Chunk chunk = this.getChunk(i >> 4, k >> 4); + if(chunk != null) { + chunk.a(enumskyblock, i & 15, j, k & 15, l); + chunk.world.m(i, j, k); + } + } + } + } + } + + private int b(EnumSkyBlock enumskyblock, int x, int y, int z) { + Chunk chunk = this.getChunk(x >> 4, z >> 4); + if (chunk != null && x >= -30000000 && z >= -30000000 && x < 30000000 && z < 30000000) { + if (y < 0) { + y = 0; + } + + if (y >= 256) { + y = 255; + } + return chunk.getBrightness(enumskyblock, x & 15, y, z & 15); + } else { + return enumskyblock.c; + } + } + + private Chunk getChunk(int x, int z) { + return this.chunks.get(LongHash.toLong(x, z)); + } + + private int a(int i, int j, int k, EnumSkyBlock enumskyblock) { + if (enumskyblock == EnumSkyBlock.SKY && this.i(i, j, k)) { + return 15; + } else { + Block block = this.getType(i, j, k); + int l = enumskyblock == EnumSkyBlock.SKY ? 0 : block.m(); + int i1 = block.k(); + + if (i1 >= 15 && block.m() > 0) { + i1 = 1; + } + + if (i1 < 1) { + i1 = 1; + } + + if (i1 >= 15) { + return 0; + } else if (l >= 14) { + return l; + } else { + for (int j1 = 0; j1 < 6; ++j1) { + int k1 = i + Facing.b[j1]; + int l1 = j + Facing.c[j1]; + int i2 = k + Facing.d[j1]; + int j2 = this.b(enumskyblock, k1, l1, i2) - i1; + + if (j2 > l) { + l = j2; + } + + if (l >= 14) { + return l; + } + } + + return l; + } + } + } + + private Block getType(int i, int j, int k) { + if (i >= -30000000 && k >= -30000000 && i < 30000000 && k < 30000000 && j >= 0 && j < 256) { + Chunk chunk = this.getChunk(i >> 4, k >> 4); + if(chunk != null) { + return chunk.getType(i & 15, j, k & 15); + } + } + return Blocks.AIR; + } + + private boolean i(int i, int j, int k) { + Chunk chunk = this.getChunk(i >> 4, k >> 4); + if(chunk != null) { + return chunk.d(i & 15, j, k & 15); + } + return true; + } +} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 86a13e755..1b5720622 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; // PaperSpigot import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,6 +42,10 @@ public class Chunk { public long s; private int x; protected net.minecraft.util.gnu.trove.map.hash.TObjectIntHashMap entityCount = new net.minecraft.util.gnu.trove.map.hash.TObjectIntHashMap(); // Spigot + // PaperSpigot start - Asynchronous light updates + public AtomicInteger pendingLightUpdates = new AtomicInteger(); + public long lightUpdateTime; + // PaperSpigot end // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking private int neighbors = 0x1 << 12; @@ -295,7 +300,7 @@ public class Chunk { private void c(int i, int j, int k, int l) { if (l > k && this.world.areChunksLoaded(i, 0, j, 16)) { for (int i1 = k; i1 < l; ++i1) { - this.world.c(EnumSkyBlock.SKY, i, i1, j); + this.world.updateLight(EnumSkyBlock.SKY, i, i1, j); // PaperSpigot - Asynchronous lighting updates } this.n = true; diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 22330c341..8aff01043 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -55,6 +55,12 @@ public class ChunkProviderServer implements IChunkProvider { } public void queueUnload(int i, int j) { + // PaperSpigot start - Asynchronous lighting updates + Chunk chunk = this.chunks.get(LongHash.toLong(i, j)); + if (chunk != null && chunk.world.paperSpigotConfig.useAsyncLighting && (chunk.pendingLightUpdates.get() > 0 || chunk.world.getTime() - chunk.lightUpdateTime < 20)) { + return; + } + // PaperSpigot end if (this.world.worldProvider.e()) { ChunkCoordinates chunkcoordinates = this.world.getSpawn(); int k = i * 16 + 8 - chunkcoordinates.x; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 042fc7756..ebb537577 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -11,6 +11,13 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +// PaperSpigot start +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import net.minecraft.util.com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.bukkit.craftbukkit.CraftChunk; +// PaperSpigot end + // CraftBukkit start import org.bukkit.Bukkit; import org.bukkit.block.BlockState; @@ -28,6 +35,10 @@ import org.bukkit.event.weather.WeatherChangeEvent; import org.bukkit.event.weather.ThunderChangeEvent; // CraftBukkit end +// Poweruser start +import net.frozenorb.LightingUpdater; +// Poweruser end + public abstract class World implements IBlockAccess { public boolean d; @@ -114,6 +125,17 @@ public abstract class World implements IBlockAccess { public static boolean haveWeSilencedAPhysicsCrash; public static String blockLocation; public List triggerHoppersList = new ArrayList(); // Spigot, When altHopperTicking, tile entities being added go through here. + // Poweruser start - only one thread, and with lower priority. Instead of one for each world + private static ExecutorService lightingExecutor; // PaperSpigot - Asynchronous lighting updates + private LightingUpdater lightingUpdater = new LightingUpdater(); + + public static ExecutorService getLightingExecutor() { + if(lightingExecutor == null) { + lightingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setPriority(Thread.NORM_PRIORITY - 1).setNameFormat("PaperSpigot - Lighting Thread").build()); + } + return lightingExecutor; + } + // Poweruser end public static long chunkToKey(int x, int z) { @@ -614,7 +636,7 @@ public abstract class World implements IBlockAccess { if (!this.worldProvider.g) { for (i1 = k; i1 <= l; ++i1) { - this.c(EnumSkyBlock.SKY, i, i1, j); + this.updateLight(EnumSkyBlock.SKY, i, i1, j); // PaperSpigot - Asynchronous lighting updates } } @@ -2383,10 +2405,10 @@ public abstract class World implements IBlockAccess { boolean flag = false; if (!this.worldProvider.g) { - flag |= this.c(EnumSkyBlock.SKY, i, j, k); + flag |= this.updateLight(EnumSkyBlock.SKY, i, j, k); // PaperSpigot - Asynchronous lighting updates } - flag |= this.c(EnumSkyBlock.BLOCK, i, j, k); + flag |= this.updateLight(EnumSkyBlock.BLOCK, i, j, k); // PaperSpigot - Asynchronous lighting updates return flag; } @@ -2431,10 +2453,10 @@ public abstract class World implements IBlockAccess { } } - public boolean c(EnumSkyBlock enumskyblock, int i, int j, int k) { + public boolean c(EnumSkyBlock enumskyblock, int i, int j, int k, Chunk chunk, List neighbors) { // PaperSpigot // CraftBukkit start - Use neighbor cache instead of looking up - Chunk chunk = this.getChunkIfLoaded(i >> 4, k >> 4); - if (chunk == null || !chunk.areNeighborsLoaded(1) /* !this.areChunksLoaded(i, j, k, 17)*/) { + //Chunk chunk = this.getChunkIfLoaded(i >> 4, k >> 4); + if (chunk == null /*|| !chunk.areNeighborsLoaded(1)*/ /* !this.areChunksLoaded(i, j, k, 17)*/) { // CraftBukkit end return false; } else { @@ -2539,11 +2561,74 @@ public abstract class World implements IBlockAccess { } } + // PaperSpigot start - Asynchronous light updates + if (chunk.world.paperSpigotConfig.useAsyncLighting) { + chunk.pendingLightUpdates.decrementAndGet(); + if (neighbors != null) { + for (Chunk neighbor : neighbors) { + neighbor.pendingLightUpdates.decrementAndGet(); + } + } + } + // PaperSpigot end this.methodProfiler.b(); return true; } } + // PaperSpigot start - Asynchronous lighting updates + public boolean updateLight(final EnumSkyBlock enumskyblock, final int x, final int y, final int z) { + final Chunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4); + if (chunk == null || !chunk.areNeighborsLoaded(2)) { // Poweruser - radius 2 + return false; + } + + if (!chunk.world.paperSpigotConfig.useAsyncLighting) { + return this.c(enumskyblock, x, y, z, chunk, null); + } + + chunk.pendingLightUpdates.incrementAndGet(); + chunk.lightUpdateTime = chunk.world.getTime(); + + final List neighbors = new ArrayList(); + // Poweruser start + int chunkx = chunk.locX; + int chunkz = chunk.locZ; + int radius = 2; + for (int cx = chunkx - radius; cx <= chunkx + radius; ++cx) { + for (int cz = chunkz - radius; cz <= chunkz + radius; ++cz) { + if(cx != chunkx || cz != chunkz) { + // Poweruser end + Chunk neighbor = this.getChunkIfLoaded(cx, cz); + if (neighbor != null) { + neighbor.pendingLightUpdates.incrementAndGet(); + neighbor.lightUpdateTime = chunk.world.getTime(); + neighbors.add(neighbor); + } + } + } + } + + if (!Bukkit.isPrimaryThread()) { + return this.c(enumskyblock, x, y, z, chunk, neighbors); + } + + // Poweruser start + getLightingExecutor().submit(new Runnable() { + @Override + public void run() { + try { + World.this.lightingUpdater.c(enumskyblock, x, y, z, chunk, neighbors); + } catch (Exception e) { + MinecraftServer.getLogger().error("Thread " + Thread.currentThread().getName() + " encountered an exception: " + e.getMessage(), e); + } + } + }); + // Poweruser end + return true; + } + // PaperSpigot end + public boolean a(boolean flag) { return false; } diff --git a/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java b/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java index 44d271ad5..ddd33e81b 100644 --- a/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java +++ b/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java @@ -206,4 +206,11 @@ public class PaperSpigotWorldConfig log( "WorldServer TickNextTickList cap set at " + tickNextTickListCap ); log( "WorldServer TickNextTickList cap always processes redstone: " + tickNextTickListCapIgnoresRedstone ); } + + public boolean useAsyncLighting; + private void useAsyncLighting() + { + useAsyncLighting = getBoolean( "use-async-lighting", false ); + log( "World async lighting: " + useAsyncLighting ); + } } -- 2.13.3