aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0417-Optimise-random-block-ticking.patch
blob: defc46a6c74ae233fe8ae5107fb3cfc01790cdfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 27 Jan 2020 21:28:00 -0800
Subject: [PATCH] Optimise random block ticking

Massive performance improvement for random block ticking.
The performance increase comes from the fact that the vast
majority of attempted block ticks (~95% in my testing) fail
because the randomly selected block is not tickable.

Now only tickable blocks are targeted, however this means that
the maximum number of block ticks occurs per chunk. However,
not all chunks are going to be targeted. The percent chance
of a chunk being targeted is based on how many tickable blocks
are in the chunk.
This means that while block ticks are spread out less, the
total number of blocks ticked per world tick remains the same.
Therefore, the chance of a random tickable block being ticked
remains the same.

diff --git a/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..3edc8e52e06a62ce9f8cc734fd7458b37cfaad91
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
@@ -0,0 +1,46 @@
+package com.destroystokyo.paper.util.math;
+
+import java.util.Random;
+
+public final class ThreadUnsafeRandom extends Random {
+
+    // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them.
+    private static final long multiplier = 0x5DEECE66DL;
+    private static final long addend = 0xBL;
+    private static final long mask = (1L << 48) - 1;
+
+    private static long initialScramble(long seed) {
+        return (seed ^ multiplier) & mask;
+    }
+
+    private long seed;
+
+    @Override
+    public void setSeed(long seed) {
+        // note: called by Random constructor
+        this.seed = initialScramble(seed);
+    }
+
+    @Override
+    protected int next(int bits) {
+        // avoid the expensive CAS logic used by superclass
+        return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits));
+    }
+
+    // Taken from
+    // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+    // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
+    // Original license is public domain
+    public static int fastRandomBounded(final long randomInteger, final long limit) {
+        // randomInteger must be [0, pow(2, 32))
+        // limit must be [0, pow(2, 32))
+        return (int)((randomInteger * limit) >>> 32);
+    }
+
+    @Override
+    public int nextInt(int bound) {
+        // yes this breaks random's spec
+        // however there's nothing that uses this class that relies on it
+        return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound);
+    }
+}
diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
index 4f0bcc096a7b1d51e4f7a3936d1dec7198002704..fc7dda277a0f80141e4de6f7efdbef0f19e19fc2 100644
--- a/src/main/java/net/minecraft/server/BlockPosition.java
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
@@ -400,6 +400,7 @@ public class BlockPosition extends BaseBlockPosition {
             return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2));
         }
 
+        public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER
         public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) {
             return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ());
         }
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 74093f7757714a85f02492dddff23cbc91c28ae0..db5f22c3df9c7302e854f1f6d66067b0afeded28 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -593,8 +593,8 @@ public class Chunk implements IChunkAccess {
         this.entities.remove(entity); // Paper
     }
 
-    @Override
-    public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) {
+    public final int getHighestBlockY(HeightMap.Type heightmap_type, int i, int j) { return this.getHighestBlock(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1
+    @Override public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { // Paper
         return ((HeightMap) this.heightMap.get(heightmap_type)).a(i & 15, j & 15) - 1;
     }
 
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
index cb72be356a2f28cfebc0cb4a822fa21d22ca5064..fbd2f6a068ee3037fc6fdc4a2f527f8b5b680afa 100644
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
@@ -7,12 +7,14 @@ import javax.annotation.Nullable;
 public class ChunkSection {
 
     public static final DataPalette<IBlockData> GLOBAL_PALETTE = new DataPaletteGlobal<>(Block.REGISTRY_ID, Blocks.AIR.getBlockData());
-    private final int yPos;
+    final int yPos; // Paper - private -> package-private
     short nonEmptyBlockCount; // Paper - package-private
-    private short tickingBlockCount;
+    short tickingBlockCount; // Paper - private -> package-private
     private short e;
     final DataPaletteBlock<IBlockData> blockIds;
 
+    final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
+
     // Paper start - Anti-Xray - Add parameters
     @Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
     public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) {
@@ -67,6 +69,9 @@ public class ChunkSection {
             --this.nonEmptyBlockCount;
             if (iblockdata1.isTicking()) {
                 --this.tickingBlockCount;
+                // Paper start
+                this.tickingList.remove(i, j, k);
+                // Paper end
             }
         }
 
@@ -78,6 +83,9 @@ public class ChunkSection {
             ++this.nonEmptyBlockCount;
             if (iblockdata.isTicking()) {
                 ++this.tickingBlockCount;
+                // Paper start
+                this.tickingList.add(i, j, k, iblockdata);
+                // Paper end
             }
         }
 
@@ -113,23 +121,29 @@ public class ChunkSection {
     }
 
     public void recalcBlockCounts() {
+        // Paper start
+        this.tickingList.clear();
+        // Paper end
         this.nonEmptyBlockCount = 0;
         this.tickingBlockCount = 0;
         this.e = 0;
-        this.blockIds.a((iblockdata, i) -> {
+        this.blockIds.forEachLocation((iblockdata, location) -> { // Paper
             Fluid fluid = iblockdata.getFluid();
 
             if (!iblockdata.isAir()) {
-                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
                 if (iblockdata.isTicking()) {
-                    this.tickingBlockCount = (short) (this.tickingBlockCount + i);
+                    this.tickingBlockCount = (short) (this.tickingBlockCount + 1);
+                    // Paper start
+                    this.tickingList.add(location, iblockdata);
+                    // Paper end
                 }
             }
 
             if (!fluid.isEmpty()) {
-                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+                this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
                 if (fluid.f()) {
-                    this.e = (short) (this.e + i);
+                    this.e = (short) (this.e + 1);
                 }
             }
 
diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
index 0e030347710df7c4bbf1ba695209ea66cec7c46d..e65fe633f5dc7df58fdc52955c23a9b9fbcdc4f6 100644
--- a/src/main/java/net/minecraft/server/DataBits.java
+++ b/src/main/java/net/minecraft/server/DataBits.java
@@ -111,4 +111,32 @@ public class DataBits {
         }
 
     }
+
+    // Paper start
+    public final void forEach(DataBitConsumer consumer) {
+        int i = 0;
+        long[] along = this.b;
+        int j = along.length;
+
+        for (int k = 0; k < j; ++k) {
+            long l = along[k];
+
+            for (int i1 = 0; i1 < this.f; ++i1) {
+                consumer.accept(i, (int) (l & this.d));
+                l >>= this.c;
+                ++i;
+                if (i >= this.e) {
+                    return;
+                }
+            }
+        }
+    }
+
+    @FunctionalInterface
+    static interface DataBitConsumer {
+
+        void accept(int location, int data);
+
+    }
+    // Paper end
 }
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
index ed77117630d54b7ad81f633110c7d2a7c59288e9..95ef96286855624590b72d69514b0fc0e08fddba 100644
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
@@ -277,6 +277,14 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
         });
     }
 
+    // Paper start
+    public void forEachLocation(DataPaletteBlock.a<T> datapaletteblock_a) {
+        this.getDataBits().forEach((int location, int data) -> {
+            datapaletteblock_a.accept(this.getDataPalette().getObject(data), location);
+        });
+    }
+    // Paper end
+
     @FunctionalInterface
     public interface a<T> {
 
diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java
index cf90ebf0614422879f5f5da05c925728f94ef8be..06333b69539a86b14b6b1efa5fee19b5bdfacfdf 100644
--- a/src/main/java/net/minecraft/server/EntityTurtle.java
+++ b/src/main/java/net/minecraft/server/EntityTurtle.java
@@ -28,7 +28,7 @@ public class EntityTurtle extends EntityAnimal {
     }
 
     public void setHomePos(BlockPosition blockposition) {
-        this.datawatcher.set(EntityTurtle.bw, blockposition);
+        this.datawatcher.set(EntityTurtle.bw, blockposition.immutableCopy()); // Paper - called with mutablepos...
     }
     public BlockPosition getHomePos() { // Paper - public
         return (BlockPosition) this.datawatcher.get(EntityTurtle.bw);
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index e7dafb74d104ecd3fa05efc7189c8298abee8ca2..cad4cd2af375d80c6ceb82541ee248c742ba68e4 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1461,10 +1461,18 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
     public abstract TagRegistry p();
 
     public BlockPosition a(int i, int j, int k, int l) {
+        // Paper start - allow use of mutable pos
+        BlockPosition.MutableBlockPosition ret = new BlockPosition.MutableBlockPosition();
+        this.getRandomBlockPosition(i, j, k, l, ret);
+        return ret.immutableCopy();
+    }
+    public final BlockPosition.MutableBlockPosition getRandomBlockPosition(int i, int j, int k, int l, BlockPosition.MutableBlockPosition out) {
+        // Paper end
         this.n = this.n * 3 + 1013904223;
         int i1 = this.n >> 2;
 
-        return new BlockPosition(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15));
+        out.setValues(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15)); // Paper - change to setValues call
+        return out; // Paper
     }
 
     public boolean isSavingDisabled() {
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 54423fd1133b313d3d2e7550f03e660b9ab6db99..d41981a5dc4beb9d63f52abea5653067144be932 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -556,7 +556,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         });
     }
 
-    public void a(Chunk chunk, int i) {
+    // Paper start - optimise random block ticking
+    private final BlockPosition.MutableBlockPosition chunkTickMutablePosition = new BlockPosition.MutableBlockPosition();
+    private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom();
+    // Paper end
+
+    public void a(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper
         ChunkCoordIntPair chunkcoordintpair = chunk.getPos();
         boolean flag = this.isRaining();
         int j = chunkcoordintpair.d();
@@ -564,10 +569,10 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         GameProfilerFiller gameprofilerfiller = this.getMethodProfiler();
 
         gameprofilerfiller.enter("thunder");
-        BlockPosition blockposition;
+        final BlockPosition.MutableBlockPosition blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
 
         if (!this.paperConfig.disableThunder && flag && this.T() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
-            blockposition = this.a(this.a(j, 0, k, 15));
+            blockposition.setValues(this.a(this.a(j, 0, k, 15))); // Paper
             if (this.isRainingAt(blockposition)) {
                 DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition);
                 boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper
@@ -590,59 +595,77 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         }
 
         gameprofilerfiller.exitEnter("iceandsnow");
-        if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
-            blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, this.a(j, 0, k, 15));
-            BlockPosition blockposition1 = blockposition.down();
+        if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking
+            // Paper start - optimise chunk ticking
+            this.getRandomBlockPosition(j, 0, k, 15, blockposition);
+            int normalY = chunk.getHighestBlockY(HeightMap.Type.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15);
+            int downY = normalY - 1;
+            blockposition.setY(normalY);
+            // Paper end
             BiomeBase biomebase = this.getBiome(blockposition);
 
-            if (biomebase.a((IWorldReader) this, blockposition1)) {
-                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.getBlockData(), null); // CraftBukkit
+            // Paper start - optimise chunk ticking
+            blockposition.setY(downY);
+            if (biomebase.a((IWorldReader) this, blockposition)) {
+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.getBlockData(), null); // CraftBukkit
+                // Paper end
             }
 
+            blockposition.setY(normalY); // Paper
             if (flag && biomebase.b(this, blockposition)) {
                 org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.getBlockData(), null); // CraftBukkit
             }
 
-            if (flag && this.getBiome(blockposition1).d() == BiomeBase.Precipitation.RAIN) {
-                this.getType(blockposition1).getBlock().c((World) this, blockposition1);
+            // Paper start - optimise chunk ticking
+            blockposition.setY(downY);
+            if (flag && this.getBiome(blockposition).d() == BiomeBase.Precipitation.RAIN) {
+                chunk.getType(blockposition).getBlock().c((World) this, blockposition);
+                // Paper end
             }
         }
 
-        gameprofilerfiller.exitEnter("tickBlocks");
-        timings.chunkTicksBlocks.startTiming(); // Paper
+        // Paper start - optimise random block ticking
+        gameprofilerfiller.exit();
         if (i > 0) {
-            ChunkSection[] achunksection = chunk.getSections();
-            int l = achunksection.length;
+            gameprofilerfiller.enter("randomTick");
+            timings.chunkTicksBlocks.startTiming(); // Paper
 
-            for (int i1 = 0; i1 < l; ++i1) {
-                ChunkSection chunksection = achunksection[i1];
+            ChunkSection[] sections = chunk.getSections();
 
-                if (chunksection != Chunk.a && chunksection.d()) {
-                    int j1 = chunksection.getYPosition();
+            for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) {
+                ChunkSection section = sections[sectionIndex];
+                if (section == null || section.tickingList.size() == 0) {
+                    continue;
+                }
 
-                    for (int k1 = 0; k1 < i; ++k1) {
-                        BlockPosition blockposition2 = this.a(j, j1, k, 15);
+                int yPos = sectionIndex << 4;
 
-                        gameprofilerfiller.enter("randomTick");
-                        IBlockData iblockdata = chunksection.getType(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
+                for (int a = 0; a < randomTickSpeed; ++a) {
+                    int tickingBlocks = section.tickingList.size();
+                    int index = this.randomTickRandom.nextInt(16 * 16 * 16);
+                    if (index >= tickingBlocks) {
+                        continue;
+                    }
 
-                        if (iblockdata.isTicking()) {
-                            iblockdata.b(this, blockposition2, this.random);
-                        }
+                    long raw = section.tickingList.getRaw(index);
+                    int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw);
+                    int randomX = location & 15;
+                    int randomY = ((location >>> (4 + 4)) & 255) | yPos;
+                    int randomZ = (location >>> 4) & 15;
 
-                        Fluid fluid = iblockdata.getFluid();
+                    BlockPosition blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ);
+                    IBlockData iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
 
-                        if (fluid.f()) {
-                            fluid.b(this, blockposition2, this.random);
-                        }
+                    iblockdata.b(this, blockposition2, this.randomTickRandom);
 
-                        gameprofilerfiller.exit();
-                    }
+                    // We drop the fluid tick since LAVA is ALREADY TICKED by the above method.
+                    // TODO CHECK ON UPDATE
                 }
             }
+            gameprofilerfiller.exit();
+            timings.chunkTicksBlocks.stopTiming(); // Paper
+            // Paper end
         }
-        timings.chunkTicksBlocks.stopTiming(); // Paper
-        gameprofilerfiller.exit();
     }
 
     protected BlockPosition a(BlockPosition blockposition) {