9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-29 03:49:15 +00:00

尝试添加section注入

This commit is contained in:
XiaoMoMi
2025-05-07 04:51:47 +08:00
parent 7c0d056637
commit af084ad46c
9 changed files with 184 additions and 94 deletions

View File

@@ -39,10 +39,10 @@ import java.util.*;
import static java.util.Objects.requireNonNull;
public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
private static int[] ordinalToIbdID;
private final Set<CEChunk> chunksToSave;
private final CEWorld ceWorld;
private static int[] ordinalToIbdID;
private static final Set<ChunkPos> BROKEN_CHUNKS = Collections.synchronizedSet(new HashSet<>());
private final Set<ChunkPos> brokenChunks = Collections.synchronizedSet(new HashSet<>());
protected FastAsyncWorldEditDelegate(EditSessionEvent event) {
super(event.getExtent());
@@ -85,7 +85,9 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
BukkitInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z));
int finalI = i;
BukkitInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
}
}
@@ -143,8 +145,8 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
@Override
protected Operation commitBefore() {
saveAllChunks();
List<ChunkPos> chunks = new ArrayList<>(BROKEN_CHUNKS);
BROKEN_CHUNKS.clear();
List<ChunkPos> chunks = new ArrayList<>(this.brokenChunks);
this.brokenChunks.clear();
Object worldServer = this.ceWorld.world().serverWorld();
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
for (ChunkPos chunk : chunks) {
@@ -172,7 +174,7 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
int chunkZ = blockZ >> 4;
int newStateId = ordinalToIbdID[newBlock.getOrdinal()];
int oldStateId = ordinalToIbdID[oldBlock.getOrdinal()];
BROKEN_CHUNKS.add(ChunkPos.of(chunkX, chunkZ));
this.brokenChunks.add(ChunkPos.of(chunkX, chunkZ));
//CraftEngine.instance().debug(() -> "Processing block at " + blockX + ", " + blockY + ", " + blockZ + ": " + oldStateId + " -> " + newStateId);
if (BlockStateUtils.isVanillaBlock(newStateId) && BlockStateUtils.isVanillaBlock(oldStateId)) return;
try {

View File

@@ -353,8 +353,15 @@ chunk-system:
# 4 = LZ4 | Blazing-Fast Blazing-Fast Low Low |
# 5 = ZSTD | Medium-Fast Fast High Medium |
compression-method: 4
# This might not work for some server forks that modify how Minecraft saves chunks.
fast-palette-injection: false
# Settings for injection
injection:
# Requires a restart to apply
# SECTION: Inject the LevelChunkSection (Faster, but may conflict with some plugins)
# PALETTE: Inject the PalettedContainer
target: PALETTE
# Enables faster injection method
# Note: May not work with certain server forks that alter chunk saving behavior
use-fast-method: false
# Auto-convert custom blocks -> vanilla blocks when unloading chunks
#
# - When ENABLED (true):

View File

@@ -51,7 +51,7 @@ 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.core.world.chunk.InjectedHolder;
import net.momirealms.craftengine.shared.ObjectHolder;
import net.momirealms.craftengine.shared.block.*;
import org.bukkit.inventory.ItemStack;
@@ -69,12 +69,14 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
public class BukkitInjector {
private static final ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V17);
private static final BukkitBlockShape STONE_SHAPE = new BukkitBlockShape(Reflections.instance$Blocks$STONE$defaultState);
private static Class<?> clazz$InjectedPalettedContainer;
private static Class<?> clazz$InjectedLevelChunkSection;
private static VarHandle varHandle$InjectedPalettedContainer$target;
@@ -100,17 +102,14 @@ public class BukkitInjector {
clazz$InjectedPalettedContainer = byteBuddy
.subclass(Reflections.clazz$PalettedContainer)
.name("net.minecraft.world.level.chunk.InjectedPalettedContainer")
.implement(InjectedPalettedContainerHolder.class)
.defineField("target", Reflections.clazz$PalettedContainer, Visibility.PRIVATE)
.defineField("ceworld", CEWorld.class, Visibility.PRIVATE)
.implement(InjectedHolder.Palette.class)
.defineField("target", Reflections.clazz$PalettedContainer, Visibility.PUBLIC)
.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)))
.and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
// TODO Requires Paper Patch
//.and(ElementMatchers.not(ElementMatchers.named("get").and(ElementMatchers.takesArguments(int.class)).and(ElementMatchers.returns(Object.class))))
)
.intercept(MethodDelegation.toField("target"))
.method(ElementMatchers.is(Reflections.method$PalettedContainer$getAndSet))
@@ -121,16 +120,33 @@ public class BukkitInjector {
.intercept(FieldAccessor.ofField("cesection"))
.method(ElementMatchers.named("ceChunk"))
.intercept(FieldAccessor.ofField("cechunk"))
.method(ElementMatchers.named("ceWorld"))
.intercept(FieldAccessor.ofField("ceworld"))
.method(ElementMatchers.named("cePos"))
.intercept(FieldAccessor.ofField("cepos"))
.make()
.load(BukkitInjector.class.getClassLoader())
.getLoaded();
varHandle$InjectedPalettedContainer$target = Objects.requireNonNull(ReflectionUtils.findVarHandle(clazz$InjectedPalettedContainer, "target", Reflections.clazz$PalettedContainer));
// Level Chunk Section
clazz$InjectedLevelChunkSection = byteBuddy
.subclass(Reflections.clazz$LevelChunkSection)
.name("net.minecraft.world.level.chunk.InjectedLevelChunkSection")
.implement(InjectedHolder.Section.class)
.defineField("cesection", CESection.class, Visibility.PRIVATE)
.defineField("cechunk", CEChunk.class, Visibility.PRIVATE)
.defineField("cepos", SectionPos.class, Visibility.PRIVATE)
.method(ElementMatchers.is(Reflections.method$LevelChunkSection$setBlockState))
.intercept(MethodDelegation.to(SetBlockStateInterceptor.INSTANCE))
.method(ElementMatchers.named("ceSection"))
.intercept(FieldAccessor.ofField("cesection"))
.method(ElementMatchers.named("ceChunk"))
.intercept(FieldAccessor.ofField("cechunk"))
.method(ElementMatchers.named("cePos"))
.intercept(FieldAccessor.ofField("cepos"))
.make()
.load(BukkitInjector.class.getClassLoader())
.getLoaded();
// State Predicate
DynamicType.Unloaded<?> alwaysTrue = byteBuddy
.subclass(Reflections.clazz$StatePredicate)
@@ -388,23 +404,33 @@ public class BukkitInjector {
// }
// }
public synchronized static void injectLevelChunkSection(Object targetSection, CESection ceSection, CEChunk chunk, SectionPos pos) {
public synchronized static void injectLevelChunkSection(Object targetSection, CESection ceSection, CEChunk chunk, SectionPos pos, Consumer<Object> callback) {
try {
Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(targetSection);
if (!(container instanceof InjectedPalettedContainerHolder)) {
InjectedPalettedContainerHolder injectedObject;
if (Config.fastPaletteInjection()) {
injectedObject = FastNMS.INSTANCE.createInjectedPalettedContainerHolder(container);
} else {
injectedObject = (InjectedPalettedContainerHolder) Reflections.UNSAFE.allocateInstance(clazz$InjectedPalettedContainer);
varHandle$InjectedPalettedContainer$target.set(injectedObject, container);
if (Config.injectionTarget()) {
Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(targetSection);
if (!(container instanceof InjectedHolder.Palette)) {
InjectedHolder.Palette injectedObject;
if (Config.fastInjection()) {
injectedObject = FastNMS.INSTANCE.createInjectedPalettedContainerHolder(container);
} else {
injectedObject = (InjectedHolder.Palette) Reflections.UNSAFE.allocateInstance(clazz$InjectedPalettedContainer);
varHandle$InjectedPalettedContainer$target.set(injectedObject, container);
}
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);
}
} else {
InjectedHolder.Section injectedObject;
if (true) {
injectedObject = FastNMS.INSTANCE.createInjectedLevelChunkSectionHolder(targetSection);
}
injectedObject.ceWorld(chunk.world());
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);
callback.accept(injectedObject);
}
} catch (Exception e) {
CraftEngine.instance().logger().severe("Failed to inject chunk section", e);
@@ -412,19 +438,30 @@ public class BukkitInjector {
}
public static boolean isSectionInjected(Object section) {
Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
return container instanceof InjectedPalettedContainerHolder;
if (Config.injectionTarget()) {
Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
return container instanceof InjectedHolder.Palette;
} else {
return section instanceof InjectedHolder.Section;
}
}
public synchronized static void uninjectLevelChunkSection(Object section) {
try {
public synchronized static Object uninjectLevelChunkSection(Object section) {
if (Config.injectionTarget()) {
Object states = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
if (states instanceof InjectedPalettedContainerHolder holder) {
Reflections.field$LevelChunkSection$states.set(section, holder.target());
if (states instanceof InjectedHolder.Palette holder) {
try {
Reflections.field$LevelChunkSection$states.set(section, holder.target());
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().severe("Failed to uninject palette", e);
}
}
} else {
if (section instanceof InjectedHolder.Section holder) {
return FastNMS.INSTANCE.constructor$LevelChunkSection(holder);
}
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().severe("Failed to inject chunk section", e);
}
return section;
}
public static class GetRecipeForMethodInterceptor1_20 {
@@ -682,58 +719,78 @@ public class BukkitInjector {
}
}
public static class SetBlockStateInterceptor {
public static final SetBlockStateInterceptor INSTANCE = new SetBlockStateInterceptor();
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable<Object> superMethod) throws Exception {
InjectedHolder.Section holder = (InjectedHolder.Section) thisObj;
int x = (int) args[0];
int y = (int) args[1];
int z = (int) args[2];
Object newState = args[3];
Object previousState = superMethod.call();
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
return previousState;
}
}
public static class GetAndSetInterceptor {
public static final GetAndSetInterceptor INSTANCE = new GetAndSetInterceptor();
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) {
InjectedPalettedContainerHolder holder = (InjectedPalettedContainerHolder) thisObj;
InjectedHolder.Palette holder = (InjectedHolder.Palette) thisObj;
Object targetStates = holder.target();
int x = (int) args[0];
int y = (int) args[1];
int z = (int) args[2];
Object previousState = FastNMS.INSTANCE.method$PalettedContainer$getAndSet(targetStates, x, y, z, args[3]);
try {
Object newState = args[3];
int stateId = BlockStateUtils.blockStateToId(newState);
CESection section = holder.ceSection();
// 如果是原版方块
if (BlockStateUtils.isVanillaBlock(stateId)) {
// 那么应该情况自定义块
ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE);
// 如果先前不是空气则标记
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);
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);
}
}
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to intercept setBlockState", e);
}
Object newState = args[3];
Object previousState = FastNMS.INSTANCE.method$PalettedContainer$getAndSet(targetStates, x, y, z, newState);
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
return previousState;
}
}
private void updateLightIfChanged(@This InjectedPalettedContainerHolder thisObj, Object previousBlockState, Object newState, @Nullable Object clientSideNewState, int y, int z, int x) throws ReflectiveOperationException {
int previousLight = BlockStateUtils.getLightEmission(previousBlockState);
int newLight = BlockStateUtils.getLightEmission(newState);
if (previousLight != newLight || (clientSideNewState != null && (BlockStateUtils.isOcclude(newState) != BlockStateUtils.isOcclude(clientSideNewState)))) {
CEWorld world = thisObj.ceWorld();
SectionPos sectionPos = thisObj.cePos();
Set<SectionPos> posSet = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, Math.max(newLight, previousLight));
world.sectionLightUpdated(posSet);
protected static void compareAndUpdateBlockState(int x, int y, int z, Object newState, Object previousState, InjectedHolder holder) {
try {
int stateId = BlockStateUtils.blockStateToId(newState);
CESection section = holder.ceSection();
// 如果是原版方块
if (BlockStateUtils.isVanillaBlock(stateId)) {
// 那么应该情况自定义块
ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE);
// 如果先前不是空气则标记
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);
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);
}
}
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to intercept setBlockState", e);
}
}
protected static void updateLightIfChanged(@This InjectedHolder thisObj, Object previousBlockState, Object newState, @Nullable Object clientSideNewState, int y, int z, int x) throws ReflectiveOperationException {
int previousLight = BlockStateUtils.getLightEmission(previousBlockState);
int newLight = BlockStateUtils.getLightEmission(newState);
if (previousLight != newLight || (clientSideNewState != null && (BlockStateUtils.isOcclude(newState) != BlockStateUtils.isOcclude(clientSideNewState)))) {
CEWorld world = thisObj.ceChunk().world();
SectionPos sectionPos = thisObj.cePos();
Set<SectionPos> posSet = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, Math.max(newLight, previousLight));
world.sectionLightUpdated(posSet);
}
}

View File

@@ -1928,7 +1928,6 @@ public class Reflections {
// field$ChunkAccess$blockEntities = targetField;
// }
@Deprecated
public static final Method method$LevelChunkSection$setBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$LevelChunkSection, clazz$BlockState, int.class, int.class, int.class, clazz$BlockState, boolean.class

View File

@@ -282,7 +282,11 @@ public class BukkitWorldManager implements WorldManager, Listener {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
BukkitInjector.uninjectLevelChunkSection(section);
Object uninjectedSection = BukkitInjector.uninjectLevelChunkSection(section);
if (uninjectedSection != section) {
sections[i] = uninjectedSection;
section = uninjectedSection;
}
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
@@ -383,7 +387,9 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
}
}
BukkitInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z));
int finalI = i;
BukkitInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
if (Config.enableRecipeSystem()) {
@SuppressWarnings("unchecked")

View File

@@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.world.InjectionTarget;
import net.momirealms.craftengine.core.world.chunk.storage.CompressionMethod;
import java.io.File;
@@ -46,6 +47,7 @@ public class Config {
private final String configVersion;
private YamlDocument config;
protected boolean firstTime = true;
protected boolean debug;
protected boolean checkUpdate;
protected boolean metrics;
@@ -100,7 +102,8 @@ public class Config {
protected boolean chunk_system$restore_custom_blocks_on_chunk_load;
protected boolean chunk_system$sync_custom_blocks_on_chunk_load;
protected int chunk_system$delay_serialization;
protected boolean chunk_system$fast_paletted_injection;
protected boolean chunk_system$injection$use_fast_method;
protected boolean chunk_system$injection$target;
protected boolean furniture$handle_invalid_furniture_on_chunk_load$enable;
protected Map<String, String> furniture$handle_invalid_furniture_on_chunk_load$mapping;
@@ -272,7 +275,10 @@ public class Config {
chunk_system$restore_custom_blocks_on_chunk_load = config.getBoolean("chunk-system.restore-custom-blocks-on-chunk-load", true);
chunk_system$sync_custom_blocks_on_chunk_load = config.getBoolean("chunk-system.sync-custom-blocks-on-chunk-load", false);
chunk_system$delay_serialization = config.getInt("chunk-system.delay-serialization", 20);
chunk_system$fast_paletted_injection = config.getBoolean("chunk-system.fast-palette-injection", false);
chunk_system$injection$use_fast_method = config.getBoolean("chunk-system.injection.use-fast-method", false);
if (firstTime) {
chunk_system$injection$target = config.getEnum("chunk-system.injection.target", InjectionTarget.class, InjectionTarget.PALETTE) == InjectionTarget.PALETTE;
}
// furniture
furniture$handle_invalid_furniture_on_chunk_load$enable = config.getBoolean("furniture.handle-invalid-furniture-on-chunk-load.enable", false);
@@ -342,6 +348,8 @@ public class Config {
plugin.logger().warn("Failed to set max chain update", e);
}
}
firstTime = false;
}
private static float getVersion(String version) {
@@ -695,8 +703,12 @@ public class Config {
return instance.chunk_system$delay_serialization;
}
public static boolean fastPaletteInjection() {
return instance.chunk_system$fast_paletted_injection;
public static boolean fastInjection() {
return instance.chunk_system$injection$use_fast_method;
}
public static boolean injectionTarget() {
return instance.chunk_system$injection$target;
}
public YamlDocument loadOrCreateYamlData(String fileName) {

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.world;
public enum InjectionTarget {
SECTION,
PALETTE
}

View File

@@ -1,11 +1,8 @@
package net.momirealms.craftengine.core.world.chunk;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.SectionPos;
public interface InjectedPalettedContainerHolder {
Object target();
public interface InjectedHolder {
CESection ceSection();
@@ -15,11 +12,15 @@ public interface InjectedPalettedContainerHolder {
void ceChunk(CEChunk chunk);
CEWorld ceWorld();
void ceWorld(CEWorld world);
SectionPos cePos();
void cePos(SectionPos pos);
interface Section extends InjectedHolder {
}
interface Palette extends InjectedHolder {
Object target();
}
}

View File

@@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.53-beta.4
config_version=31
project_version=0.0.53-beta.6
config_version=32
lang_version=12
project_group=net.momirealms
latest_supported_version=1.21.5
@@ -50,7 +50,7 @@ byte_buddy_version=1.17.5
ahocorasick_version=0.6.3
snake_yaml_version=2.4
anti_grief_version=0.15
nms_helper_version=0.64.6
nms_helper_version=0.65.11
evalex_version=3.5.0
reactive_streams_version=1.0.4
amazon_awssdk_version=2.31.23