diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java index 1b9b0bbf3..11911a153 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java @@ -49,6 +49,7 @@ import net.momirealms.craftengine.core.util.SectionPosUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.CESection; import net.momirealms.craftengine.core.world.chunk.InjectedPalettedContainerHolder; import net.momirealms.craftengine.shared.ObjectHolder; @@ -101,8 +102,9 @@ public class BukkitInjector { .name("net.minecraft.world.level.chunk.InjectedPalettedContainer") .implement(InjectedPalettedContainerHolder.class) .defineField("target", Reflections.clazz$PalettedContainer, Visibility.PRIVATE) - .defineField("cesection", CESection.class, Visibility.PRIVATE) .defineField("ceworld", CEWorld.class, Visibility.PRIVATE) + .defineField("cesection", CESection.class, Visibility.PRIVATE) + .defineField("cechunk", CEChunk.class, Visibility.PRIVATE) .defineField("cepos", SectionPos.class, Visibility.PRIVATE) .method(ElementMatchers.any() .and(ElementMatchers.not(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet))) @@ -117,6 +119,8 @@ public class BukkitInjector { .intercept(FieldAccessor.ofField("target")) .method(ElementMatchers.named("ceSection")) .intercept(FieldAccessor.ofField("cesection")) + .method(ElementMatchers.named("ceChunk")) + .intercept(FieldAccessor.ofField("cechunk")) .method(ElementMatchers.named("ceWorld")) .intercept(FieldAccessor.ofField("ceworld")) .method(ElementMatchers.named("cePos")) @@ -384,14 +388,15 @@ public class BukkitInjector { // } // } - public static void injectLevelChunkSection(Object targetSection, CESection ceSection, CEWorld ceWorld, SectionPos pos) { + public static void injectLevelChunkSection(Object targetSection, CESection ceSection, CEWorld ceWorld, CEChunk chunk, SectionPos pos) { try { Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(targetSection); if (!clazz$InjectedPalettedContainer.isInstance(container)) { InjectedPalettedContainerHolder injectedObject = (InjectedPalettedContainerHolder) Reflections.UNSAFE.allocateInstance(clazz$InjectedPalettedContainer); varHandle$InjectedPalettedContainer$target.set(injectedObject, container); - injectedObject.ceSection(ceSection); injectedObject.ceWorld(ceWorld); + injectedObject.ceChunk(chunk); + injectedObject.ceSection(ceSection); injectedObject.cePos(pos); Reflections.varHandle$PalettedContainer$data.setVolatile(injectedObject, Reflections.varHandle$PalettedContainer$data.get(container)); Reflections.field$LevelChunkSection$states.set(targetSection, injectedObject); @@ -682,16 +687,24 @@ public class BukkitInjector { Object newState = args[3]; int stateId = BlockStateUtils.blockStateToId(newState); CESection section = holder.ceSection(); + // 如果是原版方块 if (BlockStateUtils.isVanillaBlock(stateId)) { - section.setBlockState(x, y, z, EmptyBlock.INSTANCE.defaultState()); + // 那么应该情况自定义块 + ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.INSTANCE.defaultState()); + // 如果先前不是空气则标记 + if (!previous.isEmpty()) { + holder.ceChunk().setDirty(true); + } if (Config.enableLightSystem() && Config.forceUpdateLight()) { updateLightIfChanged(holder, previousState, newState, null, y, z, x); } } else { ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); - section.setBlockState(x, y, z, immutableBlockState); - if (!immutableBlockState.isEmpty()) { - if (Config.enableLightSystem()) { + ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, immutableBlockState); + // 如果之前的自定义块(空气)和当前自定义块不同 + if (previousImmutableBlockState != immutableBlockState) { + holder.ceChunk().setDirty(true); + if (Config.enableLightSystem() && !immutableBlockState.isEmpty()) { updateLightIfChanged(holder, previousState, newState, immutableBlockState.vanillaBlockState().handle(), y, z, x); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index 070737eee..bc3d7f7e8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -375,7 +375,7 @@ public class BukkitWorldManager implements WorldManager, Listener { } } } - BukkitInjector.injectLevelChunkSection(section, ceSection, ceWorld, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z)); + BukkitInjector.injectLevelChunkSection(section, ceSection, ceWorld, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z)); } if (Config.enableRecipeSystem()) { @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 6817fd512..51bc86577 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -47,7 +47,11 @@ public abstract class CEWorld { this.loadedChunkMapLock.readLock().lock(); try { for (Map.Entry entry : this.loadedChunkMap.entrySet()) { - worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), entry.getValue(), true); + CEChunk chunk = entry.getValue(); + if (chunk.dirty()) { + worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk, true); + chunk.setDirty(false); + } } } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to save world chunks", e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 41667fae2..d35de426e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -16,6 +16,7 @@ public class CEChunk { private final CESection[] sections; private final WorldHeight worldHeightAccessor; private final List entities; + private boolean dirty; public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; @@ -44,6 +45,14 @@ public class CEChunk { this.fillEmptySection(); } + public boolean dirty() { + return dirty; + } + + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + public boolean isEmpty() { if (!this.entities.isEmpty()) return false; for (CESection section : this.sections) { @@ -73,7 +82,10 @@ public class CEChunk { if (section == null) { return; } - section.setBlockState((y & 15) << 8 | (z & 15) << 4 | x & 15, state); + ImmutableBlockState previous = section.setBlockState((y & 15) << 8 | (z & 15) << 4 | x & 15, state); + if (previous != state) { + setDirty(true); + } } @Nullable diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java index 92eb3dd3e..a7f9553ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.world.chunk; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.world.BlockPos; +import org.jetbrains.annotations.ApiStatus; public class CESection { public static final int SECTION_WIDTH = 16; @@ -16,30 +17,37 @@ public class CESection { this.statesContainer = statesContainer; } - public void setBlockState(BlockPos pos, ImmutableBlockState state) { - this.setBlockState(pos.x() & 15, pos.y() & 15, pos.z() & 15, state); + @ApiStatus.Internal + public ImmutableBlockState setBlockState(BlockPos pos, ImmutableBlockState state) { + return this.setBlockState(pos.x() & 15, pos.y() & 15, pos.z() & 15, state); } - public void setBlockState(int x, int y, int z, ImmutableBlockState state) { - this.statesContainer.set((y << 4 | z) << 4 | x, state); + @ApiStatus.Internal + public ImmutableBlockState setBlockState(int x, int y, int z, ImmutableBlockState state) { + return this.setBlockState((y << 4 | z) << 4 | x, state); } - public void setBlockState(int index, ImmutableBlockState state) { - this.statesContainer.set(index, state); + @ApiStatus.Internal + public ImmutableBlockState setBlockState(int index, ImmutableBlockState state) { + return this.statesContainer.getAndSet(index, state); } + @ApiStatus.Internal public ImmutableBlockState getBlockState(BlockPos pos) { return getBlockState(pos.x() & 15, pos.y() & 15, pos.z() & 15); } + @ApiStatus.Internal public ImmutableBlockState getBlockState(int x, int y, int z) { return statesContainer.get((y << 4 | z) << 4 | x); } + @ApiStatus.Internal public ImmutableBlockState getBlockState(int index) { return statesContainer.get(index); } + @ApiStatus.Internal public PalettedContainer statesContainer() { return statesContainer; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/EmptyPaletteStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/EmptyPaletteStorage.java index 262e5dc1d..610b89419 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/EmptyPaletteStorage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/EmptyPaletteStorage.java @@ -6,35 +6,48 @@ import java.util.function.IntConsumer; public record EmptyPaletteStorage(int size) implements PaletteStorage { public static final long[] EMPTY_DATA = new long[0]; + @Override public int swap(int index, int value) { return 0; } + @Override public void set(int index, int value) { } + @Override + public int getAndSet(int index, int value) { + return 0; + } + + @Override public int get(int index) { return 0; } + @Override public long[] getData() { return EMPTY_DATA; } + @Override public int getElementBits() { return 0; } + @Override public void forEach(IntConsumer action) { for (int i = 0; i < this.size; ++i) { action.accept(0); } } + @Override public void writePaletteIndices(int[] out) { Arrays.fill(out, 0, this.size, 0); } + @Override public PaletteStorage copy() { return this; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedPalettedContainerHolder.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedPalettedContainerHolder.java index a5cbc6e31..f8d67784d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedPalettedContainerHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedPalettedContainerHolder.java @@ -11,6 +11,10 @@ public interface InjectedPalettedContainerHolder { void ceSection(CESection section); + CEChunk ceChunk(); + + void ceChunk(CEChunk chunk); + CEWorld ceWorld(); void ceWorld(CEWorld world); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PackedIntegerArray.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PackedIntegerArray.java index 80aabd580..9601bf068 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PackedIntegerArray.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PackedIntegerArray.java @@ -81,7 +81,7 @@ public class PackedIntegerArray implements PaletteStorage { int i = this.getStorageIndex(index); long l = this.data[i]; int j = (index - i * this.elementsPerLong) * this.elementBits; - this.data[i] = l & ~(this.maxValue << j) | ((long)value & this.maxValue) << j; + this.data[i] = l & ~(this.maxValue << j) | ((long) value & this.maxValue) << j; } @Override @@ -92,6 +92,15 @@ public class PackedIntegerArray implements PaletteStorage { return (int)(l >> j & this.maxValue); } + @Override + public int getAndSet(int index, int value) { + int i = this.getStorageIndex(index); + long l = this.data[i]; + int j = (index - i * this.elementsPerLong) * this.elementBits; + this.data[i] = l & ~(this.maxValue << j) | ((long) value & this.maxValue) << j; + return (int)(l >> j & this.maxValue); + } + @Override public long[] getData() { return this.data; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PaletteStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PaletteStorage.java index 3ef4e4dba..d8843e56c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PaletteStorage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PaletteStorage.java @@ -8,6 +8,8 @@ public interface PaletteStorage { void set(int index, int value); + int getAndSet(int index, int value); + int get(int index); long[] getData(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java index 3a6fb0e5f..34229ca81 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java @@ -125,6 +125,17 @@ public class PalettedContainer implements PaletteResizeListener, ReadableC return data.palette.get(data.storage.get(index)); } + public T getAndSet(int index, T state) { + this.lock(); + try { + int i = this.data.palette.index(state); + int preIndex = this.data.storage.getAndSet(index, i); + return this.data.palette.get(preIndex); + } finally { + this.unlock(); + } + } + public void set(int x, int y, int z, T value) { this.lock(); try { diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java index 6cb356a90..bc6a0acc5 100644 --- a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.mod; +import net.minecraft.world.level.chunk.PalettedContainer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode;