From 4fa7c85bce59ce346f8c71785abcaf5b0cbefb5b Mon Sep 17 00:00:00 2001 From: Samsuik Date: Sun, 16 Feb 2025 23:29:18 +0000 Subject: [PATCH] Reimplement vanilla and eigencraft redstone cache --- build-data/sakura.at | 2 + ...ock-changes-and-level-tick-scheduler.patch | 6 +- .../0006-Store-Entity-Data-State.patch | 2 +- .../features/0007-Merge-Cannon-Entities.patch | 2 +- ...0008-Replace-explosion-density-cache.patch | 4 +- .../0012-Add-maxSearch-to-getEntities.patch | 2 +- .../0014-Explosion-Durable-Blocks.patch | 2 +- .../0016-Configure-cannon-physics.patch | 2 +- ...017-Allow-explosions-to-destroy-lava.patch | 2 +- .../0018-Collide-with-non-solid-blocks.patch | 2 +- ...22-Protect-scaffolding-from-creepers.patch | 2 +- .../0024-Optimise-hopper-ticking.patch | 4 +- ...anilla-and-eigencraft-redstone-wires.patch | 196 ++++++++++++++++++ .../minecraft/world/level/Level.java.patch | 9 + .../configuration/WorldConfiguration.java | 1 - .../local/LocalConfigManager.java | 2 +- .../sakura/listener/BlockChangeTracker.java | 21 +- .../sakura/redstone/RedstoneNetwork.java | 185 +++++++++++++++++ .../redstone/RedstoneNetworkSource.java | 22 ++ .../redstone/RedstoneOriginalPower.java | 5 + .../sakura/redstone/RedstoneWireCache.java | 113 ++++++++++ .../sakura/redstone/RedstoneWireUpdate.java | 47 +++++ 22 files changed, 608 insertions(+), 25 deletions(-) create mode 100644 sakura-server/minecraft-patches/features/0028-Cache-vanilla-and-eigencraft-redstone-wires.patch create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java diff --git a/build-data/sakura.at b/build-data/sakura.at index 422ff52..d0868bf 100644 --- a/build-data/sakura.at +++ b/build-data/sakura.at @@ -18,5 +18,7 @@ protected-f net.minecraft.world.entity.Entity eyeHeight protected-f net.minecraft.world.level.ServerExplosion center public net.minecraft.server.level.ServerLevel entityTickList public net.minecraft.world.entity.Entity stuckSpeedMultiplier +public net.minecraft.world.level.Level neighborUpdater +public net.minecraft.world.level.block.RedStoneWireBlock turbo public net.minecraft.world.level.entity.EntityTickList entities public net.minecraft.world.level.material.FlowingFluid getLegacyLevel(Lnet/minecraft/world/level/material/FluidState;)I diff --git a/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch b/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch index c915c18..02a9782 100644 --- a/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch +++ b/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch @@ -17,7 +17,7 @@ index 3a926d4d34a7c68a24b9e00bcbcff271c8992ad2..259c2b2d459d86cb11ab848c77f48c38 this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 38fc85d18f737736dcdf5142c8357f9af43e4d19..1165cf61413e9a1cd4373ef5f5858e8c5a040a20 100644 +index 316dca7b4b20c6d05bf76f64a79984d10807dab5..9fbce0f31a73928a7db752e085fb3d0020fc6387 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -837,6 +837,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -32,7 +32,7 @@ index 38fc85d18f737736dcdf5142c8357f9af43e4d19..1165cf61413e9a1cd4373ef5f5858e8c protected Level( WritableLevelData levelData, diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 761fdcd4a4e18f45547afd8edff44f61c6eeacb4..7887799c39b6e5b1657aff49d7110cf14bec8a79 100644 +index 761fdcd4a4e18f45547afd8edff44f61c6eeacb4..2fb04fa82cc7b726df1e42a1cf452ebfbdd4503a 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -126,6 +126,21 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p @@ -48,7 +48,7 @@ index 761fdcd4a4e18f45547afd8edff44f61c6eeacb4..7887799c39b6e5b1657aff49d7110cf1 + + private void blockChange(BlockPos pos, BlockState newBlock, BlockState oldBlock) { + for (me.samsuik.sakura.listener.BlockChangeTracker.Listener listener : this.blockChangeListeners) { -+ if (listener.test(pos, newBlock, oldBlock)) { ++ if (listener.test(this.level, pos, newBlock, oldBlock)) { + listener.call(); + } + } diff --git a/sakura-server/minecraft-patches/features/0006-Store-Entity-Data-State.patch b/sakura-server/minecraft-patches/features/0006-Store-Entity-Data-State.patch index 767dae8..40681cb 100644 --- a/sakura-server/minecraft-patches/features/0006-Store-Entity-Data-State.patch +++ b/sakura-server/minecraft-patches/features/0006-Store-Entity-Data-State.patch @@ -35,7 +35,7 @@ index 5e48d84c5ab335975f6533472aec1e1789375bbf..30e6492e60fc7b5767d09574304ad3a3 public Entity(EntityType entityType, Level level) { this.type = entityType; diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 1165cf61413e9a1cd4373ef5f5858e8c5a040a20..5c61a690327cd1c872cdb3604cfa89d988918360 100644 +index 9fbce0f31a73928a7db752e085fb3d0020fc6387..869670abd843d6c10babf8ffaa7bfc919f8a818a 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1514,6 +1514,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/sakura-server/minecraft-patches/features/0007-Merge-Cannon-Entities.patch b/sakura-server/minecraft-patches/features/0007-Merge-Cannon-Entities.patch index 05cb130..798aaa9 100644 --- a/sakura-server/minecraft-patches/features/0007-Merge-Cannon-Entities.patch +++ b/sakura-server/minecraft-patches/features/0007-Merge-Cannon-Entities.patch @@ -276,7 +276,7 @@ index b6467f9ff64a76104076a01bfd56852ce712d95b..e6aa1d1a5fc5cd2ffc156125c4eef2d0 @Nullable diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 8688f08fa902445807fe018c44e4a3b3ca01c55e..0db34fe552d65145e5714b4a9adec20c119384e7 100644 +index 869670abd843d6c10babf8ffaa7bfc919f8a818a..5c4c2823ec6b5e2d9293b9130ddf2bc1c3c12048 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -841,6 +841,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/sakura-server/minecraft-patches/features/0008-Replace-explosion-density-cache.patch b/sakura-server/minecraft-patches/features/0008-Replace-explosion-density-cache.patch index 55f1190..4fcc988 100644 --- a/sakura-server/minecraft-patches/features/0008-Replace-explosion-density-cache.patch +++ b/sakura-server/minecraft-patches/features/0008-Replace-explosion-density-cache.patch @@ -17,7 +17,7 @@ index 8fec4a46a9cf13d832306bd87a4fe17be79bef86..dce3331c7dbbf9bb6c6342d0a760c040 // Paper start diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 410a086f2fe2b6f695dd9bcce5a1f064b239f5c2..4314234e1d7f7a0fedf0ecf29ac3284e51be88d7 100644 +index 5c4c2823ec6b5e2d9293b9130ddf2bc1c3c12048..125f61e6560790bed25e2f7aedaa189caf31a8aa 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -842,6 +842,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -114,7 +114,7 @@ index 69d490c79e30fb42da69bbd804ecaea7b88fe7b0..497409e04dc4b9366da1fbe0641b8424 this.updateNeighbours(level, pos); level.setBlocksDirty(pos, state, blockState); diff --git a/net/minecraft/world/level/block/TripWireHookBlock.java b/net/minecraft/world/level/block/TripWireHookBlock.java -index 6a7e5a642e2eaf7d5dffadb81738f7385a38c0af..dd2ceb8fd24281a6dd06084145f387cff55c6cc9 100644 +index 30b97cdcd495490ef65c2ab9dfc39a39c93002ca..e57cc9f0987d9d4cda888bc633e5f24e510ec709 100644 --- a/net/minecraft/world/level/block/TripWireHookBlock.java +++ b/net/minecraft/world/level/block/TripWireHookBlock.java @@ -182,6 +182,11 @@ public class TripWireHookBlock extends Block { diff --git a/sakura-server/minecraft-patches/features/0012-Add-maxSearch-to-getEntities.patch b/sakura-server/minecraft-patches/features/0012-Add-maxSearch-to-getEntities.patch index 92878f6..9195a06 100644 --- a/sakura-server/minecraft-patches/features/0012-Add-maxSearch-to-getEntities.patch +++ b/sakura-server/minecraft-patches/features/0012-Add-maxSearch-to-getEntities.patch @@ -83,7 +83,7 @@ index 7554c109c35397bc1a43dd80e87764fd78645bbf..d60f30f7afb15cc90c1bd4b816136d00 } } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 4314234e1d7f7a0fedf0ecf29ac3284e51be88d7..544617311ccc2573273cebba62e9503caa8a8dc3 100644 +index 125f61e6560790bed25e2f7aedaa189caf31a8aa..dc3c1d354bd4fb557f295c8dca14a31048def456 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1795,10 +1795,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/sakura-server/minecraft-patches/features/0014-Explosion-Durable-Blocks.patch b/sakura-server/minecraft-patches/features/0014-Explosion-Durable-Blocks.patch index c12343b..dfc63ca 100644 --- a/sakura-server/minecraft-patches/features/0014-Explosion-Durable-Blocks.patch +++ b/sakura-server/minecraft-patches/features/0014-Explosion-Durable-Blocks.patch @@ -41,7 +41,7 @@ index 68e50c6ade879d263424f244070677cb81c34c33..8467af4ee57b6699227370ada7bf15ca return !interactionResult.consumesAction() && context.getItemInHand().has(DataComponents.CONSUMABLE) ? super.use(context.getLevel(), context.getPlayer(), context.getHand()) diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 544617311ccc2573273cebba62e9503caa8a8dc3..7d98febf87a51fb7d9b502fa362d1bc4ac1e3eaa 100644 +index dc3c1d354bd4fb557f295c8dca14a31048def456..f7a341082d78667d34e7832f4567bbf4f3f86a00 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -843,6 +843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/sakura-server/minecraft-patches/features/0016-Configure-cannon-physics.patch b/sakura-server/minecraft-patches/features/0016-Configure-cannon-physics.patch index 36bb137..d6321bb 100644 --- a/sakura-server/minecraft-patches/features/0016-Configure-cannon-physics.patch +++ b/sakura-server/minecraft-patches/features/0016-Configure-cannon-physics.patch @@ -676,7 +676,7 @@ index 78aa0ae9ce937b7232eac1d65fd987c21489979d..e719be50ff73610046696a2105367133 if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) { this.fizz(level, pos); diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index efdcee032174af1f9a14183e9e8af3e7c4694942..d763a636ecb92a12786f49e2096a95827a3d1717 100644 +index 945f34971680bb1c90ea133431c900ad1b5a8ced..31a76dc1ee4a02ad91637b27d1927b5a5d38ddb3 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java @@ -572,6 +572,10 @@ public class RedStoneWireBlock extends Block { diff --git a/sakura-server/minecraft-patches/features/0017-Allow-explosions-to-destroy-lava.patch b/sakura-server/minecraft-patches/features/0017-Allow-explosions-to-destroy-lava.patch index af6f176..ad426b6 100644 --- a/sakura-server/minecraft-patches/features/0017-Allow-explosions-to-destroy-lava.patch +++ b/sakura-server/minecraft-patches/features/0017-Allow-explosions-to-destroy-lava.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Allow explosions to destroy lava diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java -index a4ceca6cfaaf048f3f78b18724d54780675354e7..0ed83fa6b8c8c38cc9f94a381276d300abdac839 100644 +index 8aa4debbc68530670ba6329554da5e9cf8e64a71..5d8d59fa34c9070d27e3ff38922eee465f03e663 100644 --- a/net/minecraft/world/level/ServerExplosion.java +++ b/net/minecraft/world/level/ServerExplosion.java @@ -407,6 +407,11 @@ public class ServerExplosion implements Explosion { diff --git a/sakura-server/minecraft-patches/features/0018-Collide-with-non-solid-blocks.patch b/sakura-server/minecraft-patches/features/0018-Collide-with-non-solid-blocks.patch index 0db43fd..c4aa9d7 100644 --- a/sakura-server/minecraft-patches/features/0018-Collide-with-non-solid-blocks.patch +++ b/sakura-server/minecraft-patches/features/0018-Collide-with-non-solid-blocks.patch @@ -46,7 +46,7 @@ index 47c8ed946cb2ad81a4469daf60dabc40c5e8beda..16b66b19157081c7717f73ee3dc91116 mutablePos.set(blockX, blockY, blockZ); blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index c5b840f990f9f357d4b63f7e450e91c431fd2d03..e72c6eed0280aab425ae1dfa1d104a2d87651b63 100644 +index 661f913bfa8a8bd0c4929a79745ea2e0f45098e4..a80fd65ea35590a992c6340a2f42f578afdaf1ab 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -537,6 +537,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/sakura-server/minecraft-patches/features/0022-Protect-scaffolding-from-creepers.patch b/sakura-server/minecraft-patches/features/0022-Protect-scaffolding-from-creepers.patch index a971756..323b4bb 100644 --- a/sakura-server/minecraft-patches/features/0022-Protect-scaffolding-from-creepers.patch +++ b/sakura-server/minecraft-patches/features/0022-Protect-scaffolding-from-creepers.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Protect scaffolding from creepers diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java -index 0ed83fa6b8c8c38cc9f94a381276d300abdac839..b43487bd468179a9634897002df44146e9f24c1b 100644 +index 5d8d59fa34c9070d27e3ff38922eee465f03e663..95fecb0338bc9c7b6c2ca484994f2f72370c5822 100644 --- a/net/minecraft/world/level/ServerExplosion.java +++ b/net/minecraft/world/level/ServerExplosion.java @@ -412,6 +412,11 @@ public class ServerExplosion implements Explosion { diff --git a/sakura-server/minecraft-patches/features/0024-Optimise-hopper-ticking.patch b/sakura-server/minecraft-patches/features/0024-Optimise-hopper-ticking.patch index ce434dc..96ffcf3 100644 --- a/sakura-server/minecraft-patches/features/0024-Optimise-hopper-ticking.patch +++ b/sakura-server/minecraft-patches/features/0024-Optimise-hopper-ticking.patch @@ -42,7 +42,7 @@ index 2d3721e311851c1801b090e99d4f9d0daf4e5f99..2249f5338f97471a833acddcee95f6a7 boolean isEmpty(); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 7d98febf87a51fb7d9b502fa362d1bc4ac1e3eaa..cfbec037f4314d7425e4abf14c77e57752eb2ae3 100644 +index f7a341082d78667d34e7832f4567bbf4f3f86a00..850de480eb3408b3a4468266aa7d16a09332b237 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1499,7 +1499,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -282,7 +282,7 @@ index 28e3b73507b988f7234cbf29c4024c88180d0aef..a0d247aa883553708c4b921582324255 + // Sakura end - optimise hopper ticking } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 7887799c39b6e5b1657aff49d7110cf14bec8a79..f71ae8ebf7ce494b2abf350513f826139c18eadc 100644 +index 2fb04fa82cc7b726df1e42a1cf452ebfbdd4503a..e2b684c630a44d7f84790ad305593823edb241ca 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -972,6 +972,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/sakura-server/minecraft-patches/features/0028-Cache-vanilla-and-eigencraft-redstone-wires.patch b/sakura-server/minecraft-patches/features/0028-Cache-vanilla-and-eigencraft-redstone-wires.patch new file mode 100644 index 0000000..895f384 --- /dev/null +++ b/sakura-server/minecraft-patches/features/0028-Cache-vanilla-and-eigencraft-redstone-wires.patch @@ -0,0 +1,196 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Sun, 16 Feb 2025 23:27:59 +0000 +Subject: [PATCH] Cache vanilla and eigencraft redstone wires + + +diff --git a/io/papermc/paper/redstone/RedstoneWireTurbo.java b/io/papermc/paper/redstone/RedstoneWireTurbo.java +index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..d90f6aa4557b5863eba6a206226f763c1e89dc31 100644 +--- a/io/papermc/paper/redstone/RedstoneWireTurbo.java ++++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java +@@ -658,6 +658,7 @@ public final class RedstoneWireTurbo { + // while these updates are dispatched to non-wires only, so we can + // pass null. + worldIn.getBlockState(upd.self).handleNeighborChanged(worldIn, upd.self, wire, null, false); ++ worldIn.redstoneWireCache.trackNeighbor(upd.self); // Sakura - cache vanilla and eigencraft wires + } + } + +@@ -801,6 +802,7 @@ public final class RedstoneWireTurbo { + // Perform the walk over all directly reachable redstone wire blocks, propagating wire value + // updates in a breadth first order out from the initial update received for the block at 'pos'. + breadthFirstWalk(worldIn); ++ worldIn.redstoneWireCache.stopTracking(); // Sakura - cache vanilla and eigencraft wires + + // With the whole search completed, clear the list of all known blocks. + // We do not want to keep around state information that may be changed by other code. +@@ -909,6 +911,7 @@ public final class RedstoneWireTurbo { + // bypass the new neighbor update stack. + if (worldIn.setBlock(upd.self, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) + updateNeighborShapes(worldIn, upd.self, state); ++ worldIn.redstoneWireCache.trackWirePower(upd.self, j, i); // Sakura - cache vanilla and eigencraft wires + } + } + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 539c2e465d4c89584b5bccaad18fadc41db0643a..ca21ac80c07f6aeecf01791d0eb7b5236063a0ed 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -699,6 +699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.levelTickScheduler.registerNewTask(this.explosionPositions::clear, 0); // Sakura - client visibility settings + this.levelTickScheduler.registerNewTask(this.mergeHandler::expire, 200); // Sakura - merge cannon entities + this.levelTickScheduler.registerNewTask(this.densityCache::invalidate, 0); // Sakura - explosion density cache ++ this.levelTickScheduler.registerNewTask(this.redstoneWireCache::expire, 300); // Sakura - cache vanilla and eigencraft wires + } + + // Paper start +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 850de480eb3408b3a4468266aa7d16a09332b237..56161f32e9fb379fc207b0ef2c1ba44e9516b32f 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -844,6 +844,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities + public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(); // Sakura - explosion density cache + public final me.samsuik.sakura.explosion.durable.DurableBlockManager durabilityManager = new me.samsuik.sakura.explosion.durable.DurableBlockManager(); // Sakura - explosion durable blocks ++ public final me.samsuik.sakura.redstone.RedstoneWireCache redstoneWireCache = new me.samsuik.sakura.redstone.RedstoneWireCache(this); // Sakura - cache vanilla and eigencraft wires + + protected Level( + WritableLevelData levelData, +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 31a76dc1ee4a02ad91637b27d1927b5a5d38ddb3..f1f6159fd097db461032b8bcfdaa8bc761fbf341 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -331,6 +331,12 @@ public class RedStoneWireBlock extends Block { + + newPower = event.getNewCurrent(); + ++ // Sakura start - cache vanilla and eigencraft wires ++ if (level.redstoneWireCache.tryApplyFromCache(pos, null, newPower, oldPower)) { ++ return state; ++ } ++ // Sakura end - cache vanilla and eigencraft wires ++ + if (level.getBlockState(pos) == state) { + state = state.setValue(POWER, newPower); + // [Space Walker] suppress shape updates and emit those manually to +@@ -338,6 +344,7 @@ public class RedStoneWireBlock extends Block { + if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { + turbo.updateNeighborShapes(level, pos, state); + } ++ level.redstoneWireCache.trackWirePower(pos, newPower, oldPower); // Sakura - cache vanilla and eigencraft wires + } + } + return state; +@@ -460,8 +467,14 @@ public class RedStoneWireBlock extends Block { + if (powerValue == 0) { + return 0; + } else { +- return side != Direction.UP +- && !this.getConnectionState(blockAccess, blockState, pos).getValue(PROPERTY_BY_DIRECTION.get(side.getOpposite())).isConnected() ++ // Sakura start - cache vanilla and eigencraft wires ++ if (side == Direction.UP) { ++ return powerValue; ++ } ++ final boolean updating = blockAccess instanceof Level level && level.redstoneWireCache.isWireUpdating(pos); ++ final BlockState state = updating ? blockState : this.getConnectionState(blockAccess, blockState, pos); ++ return !state.getValue(PROPERTY_BY_DIRECTION.get(side.getOpposite())).isConnected() ++ // Sakura end - cache vanilla and eigencraft wires + ? 0 + : powerValue; + } +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index 0a233c18fbad92c59b9c001574be3464f2be9d2c..80db5829a8b023ffbc25341168466a398346e1b8 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -524,6 +524,13 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.constantCollisionShape; + } + // Paper end - optimise collisions ++ // Sakura start - cache vanilla and eigencraft wires ++ private boolean specialBlock; ++ ++ public final boolean isSpecialBlock() { ++ return this.specialBlock; ++ } ++ // Sakura end - cache vanilla and eigencraft wires + + protected BlockStateBase(Block owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { + super(owner, values, propertiesCodec); +@@ -638,6 +645,7 @@ public abstract class BlockBehaviour implements FeatureElement { + } + } + // Paper end - optimise collisions ++ this.specialBlock = !Block.class.equals(this.owner.getClass()); // Sakura - cache vanilla and eigencraft wires + } + + public Block getBlock() { +diff --git a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +index 028eae2f9a459b60e92f3344091083aa93b54485..9fbf679b54088f89ac4ba727ccb645d645f01bd7 100644 +--- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -26,6 +26,13 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + this.maxChainedNeighborUpdates = maxChainedNeighborUpdates; + } + ++ // Sakura start - cache vanilla and eigencraft wires ++ @Override ++ public final int getUpdateDepth() { ++ return this.count; ++ } ++ // Sakura end - cache vanilla and eigencraft wires ++ + @Override + public void shapeUpdate(Direction direction, BlockState state, BlockPos pos, BlockPos neighborPos, int flags, int recursionLevel) { + this.addAndRun(pos, new CollectingNeighborUpdater.ShapeUpdate(direction, state, pos.immutable(), neighborPos.immutable(), flags, recursionLevel)); +@@ -83,6 +90,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + } + } + } finally { ++ this.level.redstoneWireCache.stopTracking(); // Sakura - cache vanilla and eigencraft wires + this.stack.clear(); + this.addedThisLayer.clear(); + this.count = 0; +diff --git a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java +index 2d77780b6727f82ffc3cb216ca5f2d6483496cfd..981ec4e417d07a475a19032b0efe2e188e2e7180 100644 +--- a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java ++++ b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java +@@ -27,7 +27,14 @@ public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator { + } + if (oldPower != i) { + // CraftBukkit end ++ // Sakura start - cache vanilla and eigencraft wires ++ final me.samsuik.sakura.redstone.RedstoneWireCache wireCache = level.redstoneWireCache; ++ if (wireCache.tryApplyFromCache(pos, orientation, i, oldPower)) { ++ return; ++ } + if (level.getBlockState(pos) == state) { ++ wireCache.trackWirePower(pos, i, oldPower); ++ // Sakura end - cache vanilla and eigencraft wires + level.setBlock(pos, state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(i)), 2); + } + +@@ -39,6 +46,7 @@ public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator { + } + + for (BlockPos blockPos : set) { ++ wireCache.trackNeighborsAt(blockPos); // Sakura - cache vanilla and eigencraft wires + level.updateNeighborsAt(blockPos, this.wireBlock); + } + } +diff --git a/net/minecraft/world/level/redstone/NeighborUpdater.java b/net/minecraft/world/level/redstone/NeighborUpdater.java +index 26c15c60d358273a3b369c286771c81d6f0979dd..485a5a5df6f94ea9793cc4ac8141679fa8527532 100644 +--- a/net/minecraft/world/level/redstone/NeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/NeighborUpdater.java +@@ -17,6 +17,12 @@ import net.minecraft.world.level.block.state.BlockState; + public interface NeighborUpdater { + Direction[] UPDATE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH}; + ++ // Sakura start - cache vanilla and eigencraft wires ++ default int getUpdateDepth() { ++ return 0; ++ } ++ // Sakura end - cache vanilla and eigencraft wires ++ + void shapeUpdate(Direction direction, BlockState state, BlockPos pos, BlockPos neighborPos, int flags, int recursionLevel); + + void neighborChanged(BlockPos pos, Block neighborBlock, @Nullable Orientation orientation); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch index bb37990..567cce6 100644 --- a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java +@@ -118,7 +_,7 @@ + public final List blockEntityTickers = Lists.newArrayList(); // Paper - public + public final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); +- private boolean tickingBlockEntities; ++ public boolean tickingBlockEntities; + public final Thread thread; + private final boolean isDebug; + private int skyDarken; @@ -168,6 +_,18 @@ return this.paperConfig; } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java index fbe346c..30f8308 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java @@ -14,7 +14,6 @@ import me.samsuik.sakura.physics.PhysicsVersion; import net.minecraft.Util; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java index 956276d..824ef0a 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java @@ -165,7 +165,7 @@ public final class LocalConfigManager implements LocalStorageHandler { public synchronized LocalValueConfig config(BlockPos position) { long gameTime = this.level.getGameTime(); - long ticks = this.expirationTick - gameTime; + long ticks = gameTime - this.expirationTick; if (ticks >= CONFIG_CACHE_EXPIRATION / 3) { this.chunkConfigCache.values().removeIf(pair -> pair.value().isExpired(gameTime)); this.expirationTick = gameTime; diff --git a/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java b/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java index 0efdc05..314e194 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java @@ -12,7 +12,6 @@ import net.minecraft.world.level.chunk.LevelChunk; import org.jspecify.annotations.NullMarked; import java.util.*; -import java.util.function.BiPredicate; import java.util.function.LongConsumer; @NullMarked @@ -26,7 +25,7 @@ public final class BlockChangeTracker { this.level = level; } - public long listenForChangesOnce(BiPredicate filter, Set positions, Runnable callback) { + public long listenForChangesOnce(BlockChangeFilter filter, Set positions, Runnable callback) { LongConsumer singleUseCallback = (identifier) -> { callback.run(); this.stopListening(identifier); @@ -34,7 +33,7 @@ public final class BlockChangeTracker { return this.listenForChanges(filter, positions, singleUseCallback); } - public long listenForChanges(BiPredicate filter, Set positions, LongConsumer callback) { + public long listenForChanges(BlockChangeFilter filter, Set positions, LongConsumer callback) { long identifier = this.identifier++; Listener listener = new Listener( filter, positions, identifier, callback @@ -48,8 +47,10 @@ public final class BlockChangeTracker { public void stopListening(long identifier) { Listener listener = this.identifiersInUse.remove(identifier); - for (ChunkPos chunkPos : getChunkPositions(listener.positions())) { - this.removeListenerFronChunk(chunkPos, listener); + if (listener != null) { + for (ChunkPos chunkPos : getChunkPositions(listener.positions())) { + this.removeListenerFronChunk(chunkPos, listener); + } } } @@ -88,14 +89,18 @@ public final class BlockChangeTracker { return chunkPositions; } - public record Listener(BiPredicate filter, Set positions, + public interface BlockChangeFilter { + boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock); + } + + public record Listener(BlockChangeFilter filter, Set positions, long identifier, LongConsumer callback) { public void call() { this.callback.accept(this.identifier); } - public boolean test(BlockPos pos, BlockState newBlock, BlockState oldBlock) { - return this.filter.test(newBlock, oldBlock) && this.positions.contains(pos); + public boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) { + return this.filter.test(level, pos, newBlock, oldBlock) && this.positions.contains(pos); } } } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java new file mode 100644 index 0000000..d9af4b3 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java @@ -0,0 +1,185 @@ +package me.samsuik.sakura.redstone; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.*; +import me.samsuik.sakura.utils.TickExpiry; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; +import net.minecraft.world.level.redstone.NeighborUpdater; +import net.minecraft.world.level.redstone.Orientation; +import org.jspecify.annotations.NullMarked; + +import java.util.BitSet; +import java.util.List; + +@NullMarked +public final class RedstoneNetwork { + private final List wireUpdates; + private final List updates; + private final Object2ObjectMap originalWirePower; + private final LongArrayList listeners = new LongArrayList(); + private final BitSet redundantUpdates = new BitSet(); + private final TickExpiry expiry; + + public RedstoneNetwork(List wireUpdates, List updates, Object2ObjectMap originalWirePower, TickExpiry expiry) { + this.wireUpdates = new ObjectArrayList<>(wireUpdates); + this.updates = new ObjectArrayList<>(updates); + this.originalWirePower = new Object2ObjectOpenHashMap<>(originalWirePower); + this.expiry = expiry; + } + + private static boolean hasRedstoneComponentChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) { + return newBlock.isRedstoneConductor(level, pos) != oldBlock.isRedstoneConductor(level, pos) + || newBlock.isSignalSource() != oldBlock.isSignalSource(); + } + + private static boolean hasBlockChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) { + return newBlock.getBlock() != oldBlock.getBlock(); + } + + public TickExpiry getExpiry() { + return this.expiry; + } + + private boolean isRegistered() { + return !this.listeners.isEmpty(); + } + + public boolean hasWire(BlockPos pos) { + return this.originalWirePower.containsKey(pos); + } + + public void invalidate(Level level) { + for (long identifier : this.listeners) { + level.blockChangeTracker.stopListening(identifier); + } + this.listeners.clear(); + } + + private void markNeighboringWiresForShapeUpdates(BlockPos pos, Object2ObjectMap wires) { + for (Direction direction : NeighborUpdater.UPDATE_ORDER) { + BlockPos neighborPos = pos.relative(direction); + RedstoneWireUpdate wireUpdate = wires.get(neighborPos); + //noinspection ConstantValue + if (wireUpdate != null) { + wireUpdate.updateShapes(); + } + } + } + + public boolean prepareAndRegister(Level level) { + Object2ObjectLinkedOpenHashMap processedWires = new Object2ObjectLinkedOpenHashMap<>(); + for (RedstoneWireUpdate wireUpdate : this.wireUpdates.reversed()) { + BlockPos wirePos = wireUpdate.getPosition(); + //noinspection ConstantValue + if (processedWires.putAndMoveToFirst(wirePos, wireUpdate) == null) { + // It's possible for the block below the redstone to break while the network is updating + BlockState state = level.getBlockState(wirePos); + if (state.is(Blocks.PISTON_HEAD)) { + return false; + } + } else if (this.originalWirePower.get(wirePos).firstPower() != wireUpdate.getPower()) { + // Filter out wires updates that are not the first and last update + // This significantly reduces the amount of updates when unpowering + wireUpdate.skipWireUpdate(); + } + } + + for (int i = 0; i < this.updates.size(); ++i) { + BlockPos updatePos = this.updates.get(i); + BlockState state = level.getBlockState(updatePos); + + // Filter out redundant neighbor updates + if (state.isAir() || state.liquid() || !state.isSpecialBlock() || processedWires.containsKey(updatePos)) { + this.redundantUpdates.set(i); + } + + // Look for blocks that actually need shape updates + Block block = state.getBlock(); + if (state.is(Blocks.OBSERVER) || state.liquid() || block instanceof FallingBlock || block instanceof LiquidBlockContainer) { + this.markNeighboringWiresForShapeUpdates(updatePos, processedWires); + } + } + + this.addBlockListeners(level); + return true; + } + + private void addBlockListeners(Level level) { + ObjectOpenHashSet positions = new ObjectOpenHashSet<>(this.updates); + positions.addAll(this.originalWirePower.keySet()); + + // Register block change listeners + this.listeners.add(level.blockChangeTracker.listenForChangesOnce( + RedstoneNetwork::hasRedstoneComponentChanged, positions, () -> this.invalidate(level) + )); + + this.listeners.add(level.blockChangeTracker.listenForChangesOnce( + RedstoneNetwork::hasBlockChanged, positions, this.redundantUpdates::clear + )); + } + + private boolean verifyWiresInNetwork(Level level) { + for (Object2ObjectMap.Entry wireEntry : this.originalWirePower.object2ObjectEntrySet()) { + BlockState state = level.getBlockState(wireEntry.getKey()); + if (!state.is(Blocks.REDSTONE_WIRE)) { + this.invalidate(level); + return false; + } + + if (state.getValue(RedStoneWireBlock.POWER) != wireEntry.getValue().originalPower()) { + return false; + } + } + + return true; + } + + private void performUpdates(Level level, Orientation orientation, RedStoneWireBlock wireBlock, int updateFrom, int updateTo) { + for (int updateIndex = updateFrom; updateIndex < updateTo; ++updateIndex) { + if (this.redundantUpdates.get(updateIndex)) { + continue; + } + BlockPos updatePos = this.updates.get(updateIndex); + level.getBlockState(updatePos).handleNeighborChanged(level, updatePos, wireBlock, orientation, false); + } + } + + public boolean applyFromCache(Level level) { + this.expiry.refresh(level.getGameTime()); + if (!this.isRegistered() || !this.verifyWiresInNetwork(level)) { + return false; + } + + Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, null, null); + RedStoneWireBlock wireBlock = (RedStoneWireBlock) Blocks.REDSTONE_WIRE; + int updateFrom = 0; + + for (RedstoneWireUpdate wireUpdate : this.wireUpdates) { + if (wireUpdate.canSkipWireUpdate()) { + updateFrom = wireUpdate.getUpdateIndex(); + continue; + } + + int updateTo = wireUpdate.getUpdateIndex(); + this.performUpdates(level, orientation, wireBlock, updateFrom, updateTo); + updateFrom = updateTo; + + BlockPos wirePos = wireUpdate.getPosition(); + BlockState state = level.getBlockState(wirePos); + BlockState newState = state.setValue(RedStoneWireBlock.POWER, wireUpdate.getPower()); + if (level.setBlock(wirePos, newState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE)) { + if (wireUpdate.needsShapeUpdate()) { + wireBlock.turbo.updateNeighborShapes(level, wirePos, newState); + } + } + } + + this.performUpdates(level, orientation, wireBlock, updateFrom, this.updates.size()); + return true; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java new file mode 100644 index 0000000..25bb467 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java @@ -0,0 +1,22 @@ +package me.samsuik.sakura.redstone; + +import io.papermc.paper.configuration.WorldConfiguration; +import me.samsuik.sakura.configuration.local.LocalValueConfig; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.redstone.Orientation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public record RedstoneNetworkSource(WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation, + BlockPos position, @Nullable Orientation orientation, + int updateDepth, int newPower, int oldPower) { + + public static RedstoneNetworkSource createNetworkSource(Level level, BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) { + LocalValueConfig localConfig = level.localConfig().config(pos); + WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = localConfig.redstoneImplementation; + int updateDepth = level.neighborUpdater.getUpdateDepth(); + return new RedstoneNetworkSource(redstoneImplementation, pos, orientation, updateDepth, newPower, oldPower); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java new file mode 100644 index 0000000..a7c7034 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java @@ -0,0 +1,5 @@ +package me.samsuik.sakura.redstone; + +public record RedstoneOriginalPower(int originalPower, int firstPower) { + +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java new file mode 100644 index 0000000..c50ba74 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java @@ -0,0 +1,113 @@ +package me.samsuik.sakura.redstone; + +import it.unimi.dsi.fastutil.objects.*; +import me.samsuik.sakura.utils.TickExpiry; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.redstone.NeighborUpdater; +import net.minecraft.world.level.redstone.Orientation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +@NullMarked +public final class RedstoneWireCache { + private final Map networkCache = new Object2ObjectOpenHashMap<>(); + private @Nullable RedstoneNetworkSource networkSource; + private final List wireUpdates = new ObjectArrayList<>(); + private final List updates = new ObjectArrayList<>(); + private final Object2ObjectMap originalWirePower = new Object2ObjectOpenHashMap<>(); + private final Level level; + private @Nullable RedstoneNetwork updatingNetwork; + + public RedstoneWireCache(Level level) { + this.level = level; + } + + public boolean isWireUpdating(BlockPos pos) { + return this.updatingNetwork != null && this.updatingNetwork.hasWire(pos); + } + + private boolean isTrackingWireUpdates() { + return this.networkSource != null; + } + + public boolean tryApplyFromCache(BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) { + if (!this.isTrackingWireUpdates()) { + if (this.updatingNetwork != null) { + return true; + } + + RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, pos, orientation, newPower, oldPower); + RedstoneNetwork network = this.networkCache.get(networkSource); + if (network != null) { + try { + this.updatingNetwork = network; + return network.applyFromCache(this.level); + } finally { + this.updatingNetwork = null; + } + } + + this.networkSource = networkSource; + } + return false; + } + + public void trackWirePower(BlockPos pos, int newPower, int oldPower) { + if (this.isTrackingWireUpdates()) { + this.originalWirePower.putIfAbsent(pos, new RedstoneOriginalPower(oldPower, newPower)); + this.wireUpdates.add(new RedstoneWireUpdate(pos, newPower, this.updates.size())); + } + } + + public void trackNeighbor(BlockPos pos) { + if (this.isTrackingWireUpdates()) { + this.updates.add(pos); + } + } + + public void trackNeighborsAt(BlockPos pos) { + if (this.isTrackingWireUpdates()) { + for (Direction neighbor : NeighborUpdater.UPDATE_ORDER) { + this.updates.add(pos.relative(neighbor)); + } + } + } + + public void expire(long tick) { + this.networkCache.values().removeIf(network -> { + if (network.getExpiry().isExpired(tick)) { + network.invalidate(this.level); + return true; + } + return false; + }); + } + + public void stopTracking() { + if (!this.isTrackingWireUpdates()) { + return; + } + + if (!this.wireUpdates.isEmpty()) { + // Cache expires if it has not been used in 600 ticks + TickExpiry expiration = new TickExpiry(this.level.getGameTime(), 600); + RedstoneNetwork redstoneNetwork = new RedstoneNetwork( + this.wireUpdates, this.updates, this.originalWirePower, expiration + ); + + if (redstoneNetwork.prepareAndRegister(this.level)) { + this.networkCache.put(this.networkSource, redstoneNetwork); + } + } + + this.wireUpdates.clear(); + this.updates.clear(); + this.originalWirePower.clear(); + this.networkSource = null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java new file mode 100644 index 0000000..844576c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java @@ -0,0 +1,47 @@ +package me.samsuik.sakura.redstone; + +import net.minecraft.core.BlockPos; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class RedstoneWireUpdate { + private final BlockPos position; + private final int power; + private final int updateIndex; + private boolean updateShape; + private boolean skipWire; + + public RedstoneWireUpdate(BlockPos position, int power, int updateIndex) { + this.position = position; + this.power = power; + this.updateIndex = updateIndex; + } + + public BlockPos getPosition() { + return this.position; + } + + public int getPower() { + return this.power; + } + + public int getUpdateIndex() { + return this.updateIndex; + } + + public boolean needsShapeUpdate() { + return this.updateShape; + } + + public void updateShapes() { + this.updateShape = true; + } + + public boolean canSkipWireUpdate() { + return this.skipWire; + } + + public void skipWireUpdate() { + this.skipWire = true; + } +}