diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeChunk.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeChunk.java index 11d1140dd..a98e297b4 100644 --- a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeChunk.java +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeChunk.java @@ -44,17 +44,32 @@ import java.util.function.Supplier; /** - * Fake level fake chunk :D all data related methods must redirect to fake level - * Updating procedure is same as fake leve + * Fake level fake chunk :D all data related methods must redirect to fake level Updating procedure is same as fakeLevel Porting info: + *
    + *
  1. uncomment last method section
  2. + *
  3. fix compile errors
  4. + *
  5. add override for remaining methods and sort/implement them accordingly
  6. + *
  7. comment last method section
  8. + *
+ *

*/ public class FakeChunk extends LevelChunk { private final FakeLevel fakeLevel; + // section cache + int lastY; + LevelChunkSection lastSection = null; + public FakeChunk(final FakeLevel worldIn, final int x, final int z) { super(worldIn, new ChunkPos(x, z)); this.fakeLevel = worldIn; + + // set itself to cache + fakeLevel.lastX = x; + fakeLevel.lastZ = z; + fakeLevel.lastChunk = this; } // ======================================== @@ -210,9 +225,20 @@ public boolean isYSpaceEmpty(int p_62075_, int p_62076_) @Override public LevelChunkSection[] getSections() { + // don't cache them return new LevelChunkSection[0]; } + @Override + public LevelChunkSection getSection(int yIdx) + { + if (lastY == yIdx && lastSection != null) + { + return lastSection; + } + return new FakeLevelChunkSection(this, yIdx); + } + // ======================================== // ============= NOOP METHODS ============= // ======================================== diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevel.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevel.java index 61e967c87..195d76386 100644 --- a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevel.java +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevel.java @@ -11,10 +11,13 @@ import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; import net.minecraft.core.SectionPos; +import net.minecraft.resources.ResourceKey; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.AbortableIterationConsumer.Continuation; +import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageSources; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureFlagSet; @@ -30,10 +33,12 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.TickingBlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.entity.LevelEntityGetter; import net.minecraft.world.level.gameevent.GameEvent; @@ -57,6 +62,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; /** * As much as general fake level. Features: @@ -86,13 +93,15 @@ public class FakeLevel extends Level { protected IFakeLevelBlockGetter levelSource; protected final IFakeLevelLightProvider lightProvider; + protected Level realLevel; protected final Scoreboard scoreboard; protected final boolean overrideBeLevel; protected final FakeChunkSource chunkSource; protected final FakeLevelLightEngine lightEngine; protected FakeLevelEntityGetterAdapter levelEntityGetter = FakeLevelEntityGetterAdapter.EMPTY; - // TODO: this is currently manually filled by class user - ideally if not filled yet this should get constructed from levelSource manually + // TODO: this is currently manually filled by class user - ideally if not filled yet this should get constructed from levelSource + // manually protected Map blockEntities = Collections.emptyMap(); /** @@ -100,32 +109,42 @@ public class FakeLevel extends Level */ protected BlockPos worldPos = BlockPos.ZERO; + // chunk cache + int lastX, lastZ; + ChunkAccess lastChunk = null; + /** - * @param levelSource data source, also try to set block entities/entities collections - * @param lightProvider light source - * @param scoreboard if null client level is used instead + * @param levelSource data source, also try to set block entities/entities collections + * @param lightProvider light source + * @param scoreboard if null client level is used instead * @param overrideBeLevel if true all block entities will have set level to this instance - * * @see #setBlockEntities(Map) for better block entity handling, if set then levelSource BE getter is not used * @see #setEntities(Collection) only way to add entities into fake level + * @see #setRealLevel(Level) if you want to reuse this instance */ - public FakeLevel(final IFakeLevelBlockGetter levelSource, final IFakeLevelLightProvider lightProvider, @Nullable final Scoreboard scoreboard, final boolean overrideBeLevel) + public FakeLevel(final IFakeLevelBlockGetter levelSource, + final IFakeLevelLightProvider lightProvider, + @Nullable final Scoreboard scoreboard, + final boolean overrideBeLevel) { super(new FakeLevelData(clientLevel().getLevelData(), lightProvider), clientLevel().dimension(), clientLevel().registryAccess(), clientLevel().dimensionTypeRegistration(), - () -> clientLevel().getProfiler(), - true, + clientLevel().getProfilerSupplier(), + clientLevel().isClientSide(), false, 0, 0); this.levelSource = levelSource; this.lightProvider = lightProvider; - this.scoreboard = scoreboard == null ? clientLevel().getScoreboard() : scoreboard; + this.realLevel = clientLevel(); + this.scoreboard = scoreboard; this.overrideBeLevel = overrideBeLevel; this.chunkSource = new FakeChunkSource(this); this.lightEngine = new FakeLevelLightEngine(this); + + setRealLevel(clientLevel()); } // ======================================== @@ -138,6 +157,27 @@ protected static ClientLevel clientLevel() return Minecraft.getInstance().level; } + public void setRealLevel(final Level realLevel) + { + if (Objects.equals(this.realLevel, realLevel)) + { + return; + } + + if (realLevel != null && realLevel.isClientSide != this.isClientSide) + { + throw new IllegalArgumentException("Received wrong sided realLevel - fakeLevel.isClientSide = " + this.isClientSide); + } + + this.realLevel = realLevel; + ((FakeLevelData) this.getLevelData()).vanillaLevelData = realLevel == null ? null : realLevel.getLevelData(); + } + + public Level realLevel() + { + return realLevel; + } + /** * @param levelSource new data source */ @@ -172,7 +212,7 @@ public BlockPos getWorldPos() /** * For better block entity handling in chunk methods. If set then {@link IFakeLevelBlockGetter#getBlockEntity(BlockPos) - * levelSource.getBlockEntity(BlockPos)} is not used + * levelSource.getBlockEntity(BlockPos)} is not used. Reset with empty collection * * @param blockEntities all block entities, should be data equivalent to levelSource */ @@ -182,11 +222,64 @@ public void setBlockEntities(final Map blockEntities) } /** - * @param entities all entities, their level should be this fake level instance + * @param entities all entities, their level should be this fake level instance. Reset with empty collection */ public void setEntities(final Collection entities) { - levelEntityGetter = FakeLevelEntityGetterAdapter.ofEntities(entities); + levelEntityGetter = entities.isEmpty() ? FakeLevelEntityGetterAdapter.EMPTY : FakeLevelEntityGetterAdapter.ofEntities(entities); + } + + // ======================================== + // ======= CTOR REAL LEVEL REDIRECTS ====== + // ======================================== + // Note: must have null check because super ctor + + @Override + public ResourceKey dimension() + { + return realLevel() != null ? realLevel().dimension() : super.dimension(); + } + + @Override + public RegistryAccess registryAccess() + { + return realLevel() != null ? realLevel().registryAccess() : super.registryAccess(); + } + + @Override + public DamageSources damageSources() + { + return realLevel() != null ? realLevel().damageSources() : super.damageSources(); + } + + @Override + public ProfilerFiller getProfiler() + { + return realLevel() != null ? realLevel().getProfiler() : super.getProfiler(); + } + + @Override + public Supplier getProfilerSupplier() + { + return realLevel() != null ? realLevel().getProfilerSupplier() : super.getProfilerSupplier(); + } + + @Override + public DimensionType dimensionType() + { + return realLevel() != null ? realLevel().dimensionType() : super.dimensionType(); + } + + @Override + public Holder dimensionTypeRegistration() + { + return realLevel() != null ? realLevel().dimensionTypeRegistration() : super.dimensionTypeRegistration(); + } + + @Override + public WorldBorder getWorldBorder() + { + return realLevel() != null ? realLevel().getWorldBorder() : super.getWorldBorder(); } // ======================================== @@ -221,6 +314,10 @@ public BlockState getBlockState(final BlockPos pos) @Override public ChunkAccess getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull) { + if (lastX == x && lastZ == z && lastChunk != null) + { + return lastChunk; + } return nonnull || hasChunk(x, z) ? new FakeChunk(this, x, z) : null; } @@ -230,27 +327,28 @@ public boolean hasChunk(int chunkX, int chunkZ) final int posX = SectionPos.sectionToBlockCoord(chunkX); final int posZ = SectionPos.sectionToBlockCoord(chunkZ); return levelSource.getMinX() <= posX && posX < levelSource.getMaxX() && - levelSource.getMinZ() <= posZ && posZ < levelSource.getMaxZ(); + levelSource.getMinZ() <= posZ && + posZ < levelSource.getMaxZ(); } @Override public int getBrightness(final LightLayer lightType, final BlockPos pos) { return lightProvider.forceOwnLightLevel() ? lightProvider.getBrightness(lightType, pos) : - clientLevel().getBrightness(lightType, worldPos.offset(pos)); + realLevel().getBrightness(lightType, worldPos.offset(pos)); } @Override public int getRawBrightness(BlockPos pos, int amount) { return lightProvider.forceOwnLightLevel() ? lightProvider.getRawBrightness(pos, amount) : - clientLevel().getRawBrightness(worldPos.offset(pos), amount); + realLevel().getRawBrightness(worldPos.offset(pos), amount); } @Override public int getSkyDarken() { - return lightProvider.forceOwnLightLevel() ? lightProvider.getSkyDarken() : clientLevel().getSkyDarken(); + return lightProvider.forceOwnLightLevel() ? lightProvider.getSkyDarken() : realLevel().getSkyDarken(); } @Override @@ -262,7 +360,7 @@ public boolean isDay() @Override public Scoreboard getScoreboard() { - return scoreboard; + return scoreboard == null ? realLevel().getScoreboard() : scoreboard; } @Override @@ -362,49 +460,43 @@ public String gatherChunkSourceStats() @Override public float getShade(Direction p_104703_, boolean p_104704_) { - return clientLevel().getShade(p_104703_, p_104704_); + return realLevel().getShade(p_104703_, p_104704_); } @Override public Holder getBiome(BlockPos pos) { - return clientLevel().getBiome(worldPos.offset(pos)); + return realLevel().getBiome(worldPos.offset(pos)); } @Override public BiomeManager getBiomeManager() { - return clientLevel().getBiomeManager(); + return realLevel().getBiomeManager(); } @Override public RecipeManager getRecipeManager() { - return clientLevel().getRecipeManager(); - } - - @Override - public RegistryAccess registryAccess() - { - return clientLevel().registryAccess(); + return realLevel().getRecipeManager(); } @Override public FeatureFlagSet enabledFeatures() { - return clientLevel().enabledFeatures(); + return realLevel().enabledFeatures(); } @Override public Holder getUncachedNoiseBiome(int x, int y, int z) { - return clientLevel().getUncachedNoiseBiome(x, y, z); + return realLevel().getUncachedNoiseBiome(x, y, z); } @Override public Holder getNoiseBiome(int x, int y, int z) { - return clientLevel().getNoiseBiome(x, y, z); + return realLevel().getNoiseBiome(x, y, z); } // ======================================== @@ -802,36 +894,12 @@ public void createFireworks(double p_46475_, super.createFireworks(p_46475_, p_46476_, p_46477_, p_46478_, p_46479_, p_46480_, p_46481_); } - @Override - public DamageSources damageSources() - { - return super.damageSources(); - } - - @Override - public ResourceKey dimension() - { - return super.dimension(); - } - - @Override - public DimensionType dimensionType() - { - return super.dimensionType(); - } - @Override public ResourceKey dimensionTypeId() { return super.dimensionTypeId(); } - @Override - public Holder dimensionTypeRegistration() - { - return super.dimensionTypeRegistration(); - } - @Override public void disconnect() { @@ -979,18 +1047,6 @@ public double getMaxEntityRadius() return super.getMaxEntityRadius(); } - @Override - public ProfilerFiller getProfiler() - { - return super.getProfiler(); - } - - @Override - public Supplier getProfilerSupplier() - { - return super.getProfilerSupplier(); - } - @Override public RandomSource getRandom() { @@ -1022,12 +1078,6 @@ public float getSunAngle(float p_46491_) return super.getSunAngle(p_46491_); } - @Override - public WorldBorder getWorldBorder() - { - return super.getWorldBorder(); - } - @Override public void globalLevelEvent(int p_46665_, BlockPos p_46666_, int p_46667_) { diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelChunkSection.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelChunkSection.java new file mode 100644 index 000000000..0397fef64 --- /dev/null +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelChunkSection.java @@ -0,0 +1,170 @@ +package com.ldtteam.structurize.client.fakelevel; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeResolver; +import net.minecraft.world.level.biome.Climate.Sampler; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.material.FluidState; +import java.util.function.Predicate; + +/** + * Porting: class is relatively small, just check super class manually (all of missing methods are/were just aliases) + */ +public class FakeLevelChunkSection extends LevelChunkSection +{ + private final FakeChunk fakeChunk; + private final int yIdx; + + /** + * @param fakeChunk parent chunk + * @param yIdx yLevel in chunk, multiply by section height + */ + public FakeLevelChunkSection(final FakeChunk fakeChunk, final int yIdx) + { + super(null, null); + this.fakeChunk = fakeChunk; + this.yIdx = yIdx; + + // set itself to cache + fakeChunk.lastY = yIdx; + fakeChunk.lastSection = this; + } + + private BlockPos formGlobalPos(int x, int y, int z) + { + return new BlockPos(x + fakeChunk.getPos().x * SECTION_WIDTH, y + yIdx * SECTION_HEIGHT, z + fakeChunk.getPos().z * SECTION_WIDTH); + } + + @Override + public BlockState setBlockState(int x, int y, int z, BlockState p_62995_, boolean p_62996_) + { + // this should return old value, but we don't allow changes + return getBlockState(x, y, z); + } + + @Override + public BlockState getBlockState(int x, int y, int z) + { + return fakeChunk.getBlockState(formGlobalPos(x, y, z)); + } + + @Override + public FluidState getFluidState(int x, int y, int z) + { + return fakeChunk.getFluidState(formGlobalPos(x, y, z)); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z) + { + return fakeChunk.getNoiseBiome(fakeChunk.getPos().x, yIdx * SECTION_HEIGHT, fakeChunk.getPos().z); + } + + @Override + public PalettedContainerRO> getBiomes() + { + // TODO: need our own? use clientLevel? + return null; + } + + @Override + public PalettedContainer getStates() + { + // TODO: need our own + return null; + } + + @Override + public boolean hasOnlyAir() + { + return false; + } + + @Override + public boolean isRandomlyTicking() + { + return false; + } + + @Override + public boolean isRandomlyTickingBlocks() + { + return false; + } + + @Override + public boolean isRandomlyTickingFluids() + { + return false; + } + + @Override + public boolean maybeHas(Predicate p_63003_) + { + // hard to say, so maybe yes + return true; + } + + @Override + public int getSerializedSize() + { + // technically noop, vanilla uses it for network sync + return 0; + } + + @Override + public void read(FriendlyByteBuf p_63005_) + { + // Noop + } + + @Override + public void readBiomes(FriendlyByteBuf p_275669_) + { + // Noop + } + + @Override + public void recalcBlockCounts() + { + // Noop + } + + @Override + public void release() + { + // Noop + } + + @Override + public void acquire() + { + // Noop + } + + @Override + public void fillBiomesFromNoise(BiomeResolver p_282075_, Sampler p_283084_, int p_282310_, int p_281510_, int p_283057_) + { + // Noop + } + + @Override + public void write(FriendlyByteBuf p_63012_) + { + // Noop + } + + /* + @Override + public BlockState setBlockState(int p_62987_, int p_62988_, int p_62989_, BlockState p_62990_) + { + return super.setBlockState(p_62987_, p_62988_, p_62989_, p_62990_); + } + */ +} diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelData.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelData.java index 8e5f1a53f..b99e7a8c3 100644 --- a/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelData.java +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/FakeLevelData.java @@ -10,7 +10,7 @@ */ public class FakeLevelData implements WritableLevelData { - protected final LevelData vanillaLevelData; + protected LevelData vanillaLevelData; protected final IFakeLevelLightProvider lightProvider; protected FakeLevelData(final LevelData vanillaLevelData, final IFakeLevelLightProvider lightProvider) diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/SingleBlockFakeLevel.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/SingleBlockFakeLevel.java new file mode 100644 index 000000000..f06f6974a --- /dev/null +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/SingleBlockFakeLevel.java @@ -0,0 +1,183 @@ +package com.ldtteam.structurize.client.fakelevel; + +import net.minecraft.CrashReportCategory; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.registries.ForgeRegistries; +import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Simple implementation of {@link IFakeLevelBlockGetter} mostly for usage in methods where {@link Level} is needed for virtual + * BE/entities etc. + */ +public class SingleBlockFakeLevel extends FakeLevel +{ + /** + * Creates simple fakeLevel instance + * + * @param realLevel actual valid vanilla instance to provide eg. registries + */ + public SingleBlockFakeLevel(final Level realLevel) + { + super(new SingleBlockFakeLevelGetter(), IFakeLevelLightProvider.USE_CLIENT_LEVEL, null, true); + super.setRealLevel(realLevel); + } + + @Override + public SingleBlockFakeLevelGetter getLevelSource() + { + return (SingleBlockFakeLevelGetter) super.getLevelSource(); + } + + /** + * Do not forget to unset to prevent potential memory leaks + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @see #unset(FakeLevel, BlockEntity) + * @see FakeLevel#setEntities(Collection) FakeLevel#setEntities(Collection) if you want to add entities, do not forget to reset + */ + public void prepare(final BlockState blockState, @Nullable final BlockEntity blockEntity, final Level realLevel) + { + getLevelSource().blockEntity = blockEntity; + getLevelSource().blockState = blockState; + setRealLevel(realLevel); + + if (blockEntity != null) + { + blockEntity.setLevel(this); + } + } + + /** + * @param blockEntity to unlink level if needed + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + */ + public void unset(@Nullable final BlockEntity blockEntity) + { + getLevelSource().blockEntity = null; + getLevelSource().blockState = null; + setRealLevel(null); + + if (blockEntity != null) + { + try + { + blockEntity.setLevel(null); + } + catch (final NullPointerException e) + { + // setLevel impls sometimes violates nullability of level field + } + } + } + + /** + * See related methods for more information. + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @param action context action + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + * @see #unset(FakeLevel, BlockEntity) + */ + public void withFakeLevelContext(final BlockState blockState, + @Nullable final BlockEntity blockEntity, + final Level realLevel, + final Consumer action) + { + prepare(blockState, blockEntity, realLevel); + action.accept(this); + unset(blockEntity); + } + + /** + * See related methods for more information. + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @param action context action + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + * @see #unset(FakeLevel, BlockEntity) + */ + public T useFakeLevelContext(final BlockState blockState, + @Nullable final BlockEntity blockEntity, + final Level realLevel, + final Function action) + { + prepare(blockState, blockEntity, realLevel); + final T result = action.apply(this); + unset(blockEntity); + return result; + } + + public static class SingleBlockFakeLevelGetter implements IFakeLevelBlockGetter + { + public BlockState blockState = null; + public BlockEntity blockEntity = null; + + @Override + public BlockEntity getBlockEntity(final BlockPos pos) + { + return blockEntity; + } + + @Override + public BlockState getBlockState(final BlockPos pos) + { + return blockState; + } + + @Override + public int getHeight() + { + return 1; + } + + @Override + public short getSizeX() + { + return 1; + } + + @Override + public short getSizeZ() + { + return 1; + } + + @Override + public void describeSelfInCrashReport(final CrashReportCategory category) + { + category.setDetail("Single block", blockState::toString); + category.setDetail("Single block entity type", + () -> blockEntity == null ? null : ForgeRegistries.BLOCK_ENTITY_TYPES.getKey(blockEntity.getType()).toString()); + } + } + + public static class SidedSingleBlockFakeLevel + { + private SingleBlockFakeLevel client; + private SingleBlockFakeLevel server; + + public SingleBlockFakeLevel get(final Level realLevel) + { + if (realLevel.isClientSide()) + { + return client != null ? client : (client = new SingleBlockFakeLevel(realLevel)); + } + else + { + return server != null ? server : (server = new SingleBlockFakeLevel(realLevel)); + } + } + } +}