From 8fae17b9a451438584bb0c0a7fa1095c0ded97ca Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sat, 13 Sep 2025 15:40:51 -0400 Subject: [PATCH 01/22] Work on worlgen framework some more --- .../cardinalstar/cubicchunks/CubicChunks.java | 9 +- .../cubicchunks/api/util/Box.java | 4 +- .../api/worldgen/CubeGeneratorsRegistry.java | 137 ++++--------- .../early/client/MixinEntityRenderer.java | 108 +++++++++++ .../mixin/early/common/MixinChunk_Cubes.java | 9 +- .../modcompat/ModWorldgenInit.java | 17 -- .../server/CubeProviderServer.java | 6 + .../cubicchunks/util/DependencyGraph.java | 104 ++++++++++ .../world/core/IColumnInternal.java | 2 + .../world/cube/blockview/EBSBlockView.java | 2 +- .../world/worldgen/FeatureCubicGenerator.java | 99 ++++++++++ .../world/worldgen/MapGenCaveFluids.java | 96 +++++++++ .../world/worldgen/MapGenCavesCubic.java | 183 ++++-------------- ...ator.java => SeedBasedCubicGenerator.java} | 4 +- .../world/worldgen/WorldGenerators.java | 40 ++++ .../world/worldgen/WorldgenFeature.java | 143 ++++++++++++++ .../compat}/DeepslateCubePopulator.java | 2 +- .../VanillaCompatibilityGenerator.java | 8 +- 18 files changed, 702 insertions(+), 271 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java rename src/main/java/com/cardinalstar/cubicchunks/world/worldgen/{SeedBasedCubicPopulator.java => SeedBasedCubicGenerator.java} (98%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java rename src/main/java/com/cardinalstar/cubicchunks/{modcompat/efr => world/worldgen/compat}/DeepslateCubePopulator.java (96%) diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index 1e5b3e78..5028e436 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -50,17 +50,21 @@ import com.cardinalstar.cubicchunks.util.CompatHandler; import com.cardinalstar.cubicchunks.util.SideUtils; import com.cardinalstar.cubicchunks.world.type.VanillaCubicWorldType; +import com.cardinalstar.cubicchunks.world.worldgen.WorldGenerators; import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; import com.cardinalstar.cubicchunks.worldgen.WorldgenHangWatchdog; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; - import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.Mod; -import cpw.mods.fml.common.event.*; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; +import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.network.NetworkCheckHandler; import cpw.mods.fml.common.network.NetworkRegistry; @@ -151,6 +155,7 @@ public String call() throws Exception { Loader.instance() .activeModContainer()); holder.testVanillaAcceptance(); + WorldGenerators.init(); } @Mod.EventHandler diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java index 38e4cf21..58bbf051 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java @@ -77,8 +77,8 @@ public boolean contains(int xmin, int ymin, int zmin, int xmax, int ymax, int zm return x1 <= xmin && x2 >= xmax && y1 <= ymin && y2 >= ymax && z1 <= zmin && z2 >= zmax; } - public boolean containsCube(int cubeX, int cubeY, int cubeZ) { - return contains(cubeX * 16, cubeY * 16, cubeZ * 16, cubeX * 16 + 15, cubeY * 16 + 15, cubeZ * 16 + 15); + public boolean contains(int x, int y, int z) { + return x1 <= x && x <= x2 && y1 <= y && y <= y2 && z1 <= z && z <= z2; } public void forEachPoint(XYZFunction function) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java index c40f0f54..f7d5ab1b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java @@ -24,22 +24,17 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Random; -import java.util.TreeSet; import java.util.function.BiConsumer; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubeTerrainGenerator; import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.cardinalstar.cubicchunks.util.DependencyGraph; import com.cardinalstar.cubicchunks.world.cube.Cube; -import com.cardinalstar.cubicchunks.world.worldgen.MapGenCavesCubic; import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; -import com.github.bsideup.jabel.Desugar; import com.google.common.base.Preconditions; public class CubeGeneratorsRegistry { @@ -56,121 +51,77 @@ public class CubeGeneratorsRegistry { private static final Collection>> columnLoadingCallbacksView = Collections .unmodifiableCollection(columnLoadingCallbacks); - private static final TreeSet> sortedVanillaGeneratorList = new TreeSet<>(); + private static final DependencyGraph> vanillaGenerators = new DependencyGraph<>(); - private static final TreeSet sortedPopulatorList = new TreeSet<>(); + private static final DependencyGraph vanillaPopulators = new DependencyGraph<>(); - private static final TreeSet sortedVanillaPopulatorList = new TreeSet<>(); - - @Desugar - private record GeneratorWrapper(ICubeTerrainGenerator generator, int weight) implements Comparable> { - - @Override - public int compareTo(GeneratorWrapper o) { - return Integer.compare(weight, o.weight); - } - } + /** + * Register a world generator that runs exclusively in the vanilla compatibility generator. This will not run for + * other world types. + */ + public static void registerVanillaGenerator(String name, ICubeTerrainGenerator generator, String... dependencies) { + Preconditions.checkNotNull(name); + Preconditions.checkNotNull(generator); - @Desugar - private record PopulatorWrapper(ICubicPopulator populator, int weight) implements Comparable { + vanillaGenerators.addObject(name, generator); - @Override - public int compareTo(PopulatorWrapper o) { - return Integer.compare(weight, o.weight); + for (String dep : dependencies) { + vanillaGenerators.addDependency(name, dep); } } /** - * Register a world generator that runs exclusively in the vanilla compatibility generator. This will not run for - * other world types. + * Adds a dependency between vanilla terrain generators. The dependency will be ran before the given generator. */ - public static void registerVanillaGenerator(ICubeTerrainGenerator generator, int priority) { + public static void addVanillaGeneratorDependency(String generator, String dependency) { Preconditions.checkNotNull(generator); - sortedVanillaGeneratorList.add(new GeneratorWrapper<>(generator, priority)); + Preconditions.checkNotNull(dependency); + + vanillaGenerators.addDependency(generator, dependency); } - /** - * Callback hook for cube gen - if your mod wishes to add extra mod related - * generation to the world call this - * - * @param generator The generator that invoked this event - * @param cube The cube to generate - */ public static void generateVanillaCube(VanillaCompatibilityGenerator generator, World world, Cube cube) { - for (GeneratorWrapper wrapper : sortedVanillaGeneratorList) { - wrapper.generator.generate(generator, world, cube); + List> generators = vanillaGenerators.sorted(); + + for (int i = 0, generatorsSize = generators.size(); i < generatorsSize; i++) { + ICubeTerrainGenerator cubeGen = generators.get(i); + + cubeGen.generate(generator, world, cube); } } /** - * Register a world populator - something that inserts new block types into the world on population stage - * - * @param populator the populator - * @param weight a weight to assign to this populator. Heavy weights tend to sink to the bottom of - * list of world populator (i.e. they run later) + * Register a world populator that runs exclusively in the vanilla compatibility populator. This will not run for + * other world types. */ - public static void registerVanillaPopulator(ICubicPopulator populator, int weight) { + public static void registerVanillaPopulator(String name, ICubicPopulator populator, String... dependencies) { + Preconditions.checkNotNull(name); Preconditions.checkNotNull(populator); - sortedVanillaPopulatorList.add(new PopulatorWrapper(populator, weight)); - } - /** - * Callback hook for cube gen - if your mod wishes to add extra mod related - * generation to the world call this - * - * @param world The {@link ICubicWorld} we're generating for - * @param pos is position of the populated cube - */ - public static void populateVanillaWorld(World world, CubePos pos) { - for (PopulatorWrapper wrapper : sortedVanillaPopulatorList) { - wrapper.populator.generate(world, pos); + vanillaPopulators.addObject(name, populator); + + for (String dep : dependencies) { + vanillaPopulators.addDependency(name, dep); } } /** - * Register a world populator - something that inserts new block types into the world on population stage - * - * @param populator the populator - * @param weight a weight to assign to this populator. Heavy weights tend to sink to the bottom of - * list of world populator (i.e. they run later) + * Adds a dependency between vanilla terrain populators. The dependency will be ran before the given populator. */ - public static void registerPopulator(ICubicPopulator populator, int weight) { + public static void addVanillaPopulatorDependency(String populator, String dependency) { Preconditions.checkNotNull(populator); - sortedPopulatorList.add(new PopulatorWrapper(populator, weight)); - } + Preconditions.checkNotNull(dependency); - /** - * Callback hook for cube gen - if your mod wishes to add extra mod related - * generation to the world call this - * - * @param random the cube specific {@link Random}. - * @param pos is position of the populated cube - * @param world The {@link ICubicWorld} we're generating for - */ - public static void populateWorld(World world, Random random, CubePos pos) { - for (PopulatorWrapper wrapper : sortedPopulatorList) { - wrapper.populator.generate(world, pos); - } + vanillaPopulators.addDependency(populator, dependency); } - /** - * Populators added here will be launched prior to any other. It is - * recommended to use this function in init or pre init event of a mod. - * - * @param populator populator instance to register - */ - public static void registerForCompatibilityGenerator(ICubicPopulator populator) { - if (!customPopulatorsForFlatCubicGenerator.contains(populator)) - customPopulatorsForFlatCubicGenerator.add(populator); - } + public static void populateVanillaCube(World world, CubePos pos) { + List populators = vanillaPopulators.sorted(); - static { - registerVanillaGenerator(new MapGenCavesCubic(), 1); - } + for (int i = 0, populatorsSize = populators.size(); i < populatorsSize; i++) { + ICubicPopulator cubeGen = populators.get(i); - public static void populateVanillaCubic(World world, ICube cube) { - for (ICubicPopulator populator : customPopulatorsForFlatCubicGenerator) { - populator.generate(world, cube.getCoords()); + cubeGen.generate(world, pos); } } @@ -181,8 +132,7 @@ public static void populateVanillaCubic(World world, ICube cube) { * * @param cubeCallback the callback to be registered */ - public static void registerCubeAsyncLoadingCallback( - BiConsumer> cubeCallback) { + public static void registerCubeAsyncLoadingCallback(BiConsumer> cubeCallback) { cubeLoadingCallbacks.add(cubeCallback); } @@ -193,8 +143,7 @@ public static void registerCubeAsyncLoadingCallback( * * @param columnCallback the callback to be registered */ - public static void registerColumnAsyncLoadingCallback( - BiConsumer> columnCallback) { + public static void registerColumnAsyncLoadingCallback(BiConsumer> columnCallback) { columnLoadingCallbacks.add(columnCallback); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java new file mode 100644 index 00000000..7553187e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java @@ -0,0 +1,108 @@ +package com.cardinalstar.cubicchunks.mixin.early.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.Vec3; +import net.minecraft.world.WorldProvider; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.cardinalstar.cubicchunks.util.MathUtil; +import com.cardinalstar.cubicchunks.util.Mods; +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import ganymedes01.etfuturum.spectator.SpectatorMode; + +@Mixin(EntityRenderer.class) +public class MixinEntityRenderer { + + @Shadow + private Minecraft mc; + + @Shadow + private float fogColorRed; + + @Shadow + private float fogColorGreen; + + @Shadow + private float fogColorBlue; + + // Redirects the dot product that changes the fog hue when you look at the sun to only look at the skylight at the + // player's feet instead of the direction the player is facing. + @Redirect(method = "updateFogColor", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Vec3;dotProduct(Lnet/minecraft/util/Vec3;)D")) + public double disableDotProduct(Vec3 instance, Vec3 vec, @Local(argsOnly = true) float partialTicks) { + EntityLivingBase player = this.mc.renderViewEntity; + + int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; + + return skylight / 16d; + } + + // Makes underground fog darker + @Inject(method = "updateFogColor", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glClearColor(FFFF)V", shift = At.Shift.BEFORE, remap = false)) + public void makeFogDarkerUnderground(float partialTicks, CallbackInfo ci) { + + EntityLivingBase player = this.mc.renderViewEntity; + + double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double)partialTicks; + + float depthColour = (float) MathUtil.clamp(playerY / 32d, 0.25, 1); + + this.fogColorRed *= depthColour; + this.fogColorGreen *= depthColour; + this.fogColorBlue *= depthColour; + } + + // Disable existing void fog logic entirely for cubic worlds + @Redirect(method = "setupFog", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldProvider;getWorldHasVoidParticles()Z")) + public boolean disableVoidFog(WorldProvider instance) { + if (!((ICubicWorld)this.mc.theWorld).isCubicWorld()) { + return instance.getWorldHasVoidParticles(); + } + + return false; + } + + // Re-add custom void fog with a better curve below y=0 + @WrapOperation(method = "setupFog", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/EntityRenderer;farPlaneDistance:F", ordinal = 1)) + public float modifyVoidFog(EntityRenderer instance, Operation original, @Local(argsOnly = true) float partialTicks) { + float farPlaneDistance = original.call(instance); + + if (!this.mc.theWorld.provider.getWorldHasVoidParticles() || !((ICubicWorld)this.mc.theWorld).isCubicWorld()) { + return farPlaneDistance; + } + + EntityClientPlayerMP player = this.mc.thePlayer; + + if (player.capabilities.isCreativeMode) return farPlaneDistance; + + if (Mods.EtFuturumRequiem.isModLoaded()) { + if (SpectatorMode.isSpectator(player)) return farPlaneDistance; + } + + int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; + double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double)partialTicks; + + double fogStrength = skylight / 16.0D + Math.max(playerY / 32.0D, 0.25); + + if (fogStrength < 1.0D) { + if (fogStrength < 0.0D) { + fogStrength = 0.0D; + } + + farPlaneDistance *= (float) (fogStrength * fogStrength); + } + + return farPlaneDistance; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java index afefbd1a..6545733c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java @@ -176,8 +176,7 @@ public T getWorldObj() { @Nullable private ExtendedBlockStorage getEBS_CubicChunks(int index) { if (!isColumn) { - // vanilla case, subtract minHeight for extended height support - return storageArrays[index - blockToCube(getWorldObj().getMinHeight())]; + return storageArrays[blockToCube(index)]; } if (cachedCube != null && cachedCube.getY() == index) { return cachedCube.getStorage(); @@ -306,6 +305,10 @@ private int getInitChunkLoopEnd(int _16, World world, Block[] blocks, byte[] met return _16; } + public void chunk_internal$setColumn(boolean isColumn) { + this.isColumn = isColumn; + } + public Block[] chunk_internal$getCompatGenerationBlockArray() { return compatGenerationBlockArray; } @@ -522,7 +525,7 @@ private int getBlock_getMaxHeight(ExtendedBlockStorage[] ebs) { args = "array=get", target = "Lnet/minecraft/world/chunk/Chunk;storageArrays:[Lnet/minecraft/world/chunk/storage/ExtendedBlockStorage;")) private ExtendedBlockStorage getBlock_getStorage(ExtendedBlockStorage[] ebs, int y) { - return getEBS_CubicChunks(y); + return isColumn ? getEBS_CubicChunks(y) : ebs[y]; } // ============================================== diff --git a/src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java b/src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java deleted file mode 100644 index 4392a455..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cardinalstar.cubicchunks.modcompat; - -import com.cardinalstar.cubicchunks.util.Mods; -import cpw.mods.fml.common.Optional; - -public class ModWorldgenInit { - - public static void init() { - - } - - @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) - private static void initEFR() { -// CubeGeneratorsRegistry.registerVanillaGenerator(); - } - -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index 486352b4..9e6eabdc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -380,6 +380,12 @@ private void doEagerLoading() { cubes++; } + long delta = System.nanoTime() - start; + + if (delta > MAX_NS_SPENT_LOADING * 2) { + CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); + } + if (columns > 0) { CubicChunks.LOGGER.info("Processed {} eager column loads this tick", columns); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java new file mode 100644 index 00000000..245aa4e7 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -0,0 +1,104 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + +public class DependencyGraph { + + private final Object2ObjectOpenHashMap objects = new Object2ObjectOpenHashMap<>(); + + private final Multimap dependencies = MultimapBuilder.hashKeys().hashSetValues().build(); + + private List cachedSorted; + + public void addObject(String name, T value) { + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(value, "value must not be null"); + + objects.put(name, value); + cachedSorted = null; + } + + public void addDependency(String object, String dependsOn) { + Objects.requireNonNull(object, "object must not be null"); + Objects.requireNonNull(dependsOn, "dependsOn must not be null"); + + dependencies.put(object, dependsOn); + cachedSorted = null; + } + + public void removeDependency(String object, String dependsOn) { + Objects.requireNonNull(object, "object must not be null"); + Objects.requireNonNull(dependsOn, "dependsOn must not be null"); + + dependencies.remove(object, dependsOn); + cachedSorted = null; + } + + public List sorted() { + if (cachedSorted != null) return cachedSorted; + + ObjectLinkedOpenHashSet path = new ObjectLinkedOpenHashSet<>(); + + for (String node : dependencies.keys()) { + preventCyclicDeps(node, path); + } + + List out = new ArrayList<>(); + ObjectLinkedOpenHashSet added = new ObjectLinkedOpenHashSet<>(); + + ObjectLinkedOpenHashSet remaining = new ObjectLinkedOpenHashSet<>(objects.keySet()); + + while (!remaining.isEmpty()) { + Iterator iter = remaining.iterator(); + + iterdeps: + while (iter.hasNext()) { + String curr = iter.next(); + + for (String dep : dependencies.get(curr)) { + if (!added.contains(dep)) { + continue iterdeps; + } + } + + iter.remove(); + + added.add(curr); + out.add(objects.get(curr)); + } + } + + cachedSorted = ImmutableList.copyOf(out); + + return cachedSorted; + } + + private void preventCyclicDeps(String node, Set path) { + if (path.contains(node)) { + throw new IllegalStateException(node + + " has a cyclic dependency with itself. The path is: " + path.stream().reduce("", (s, s2) -> s + ", " + s2)); + } + + if (!objects.containsKey(node)) { + throw new IllegalStateException(node + " is present in the dependency graph but does not have a matching object"); + } + + path.add(node); + + for (String deps : dependencies.get(node)) { + preventCyclicDeps(deps, path); + } + + path.remove(node); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java b/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java index a5bb50da..3052340f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java @@ -31,6 +31,8 @@ public interface IColumnInternal extends IColumn { + void setColumn(boolean isColumn); + Block[] getCompatGenerationBlockArray(); byte[] getCompatGenerationByteArray(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/EBSBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/EBSBlockView.java index 0782ce97..98184e26 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/EBSBlockView.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/EBSBlockView.java @@ -11,7 +11,7 @@ public class EBSBlockView implements IBlockView { - public static final Box EBS_BOUNDS = new Box(0, 0, 0, 16, 16, 16); + public static final Box EBS_BOUNDS = new Box(0, 0, 0, 15, 15, 15); private final ExtendedBlockStorage ebs; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java new file mode 100644 index 00000000..9f026f12 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java @@ -0,0 +1,99 @@ +package com.cardinalstar.cubicchunks.world.worldgen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import com.cardinalstar.cubicchunks.api.util.Box; +import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + +public abstract class FeatureCubicGenerator extends SeedBasedCubicGenerator, TGen> { + + protected FeatureCubicGenerator(int range) { + super(range); + } + + private final ArrayList seeds = new ArrayList<>(); + + @Override + protected final void getSeeds(Random rng, int cubeX, int cubeY, int cubeZ, List> features) { + getSeedsImpl(rng, cubeX, cubeY, cubeZ, seeds); + + for (TSeed seed : seeds) { + generateFeature(features, seed); + } + + seeds.clear(); + } + + private void generateFeature(List> features, TSeed seed) { + WorldgenFeature feature = new WorldgenFeature<>(getSeedX(seed), getSeedY(seed), getSeedZ(seed), seed); + + generateSeed(seed, feature); + + features.add(feature); + + // Must be done after the seed is generated + Collection branches = getSeedBranches(seed); + + if (!branches.isEmpty()) { + for (TSeed branch : branches) { + generateFeature(features, branch); + } + } + } + + /** + * Checks if the given cube has any features, and adds them to the list. The seed is stored in a timed cache, so + * they must be immutable. + * @param rng An RNG that is seeded to a deterministic value for this cube. + */ + protected abstract void getSeedsImpl(Random rng, int cubeX, int cubeY, int cubeZ, List seeds); + + /** + * Walks through the feature's blocks and inserts the operations into the feature. This is only done once per seed. + */ + protected abstract void generateSeed(TSeed seed, WorldgenFeature feature); + + @Override + protected final void generate(Random rng, WorldgenFeature feature, WorldView worldView) { + Box box = worldView.getBounds(); + + CubePos pos = worldView.getCube(); + + if (feature.affects(pos.getX(), pos.getY(), pos.getZ()) && shouldGenerate(worldView, feature)) { + for (var p : feature.getOperations(pos.getX(), pos.getY(), pos.getZ())) { + int x = p.left().x() + box.getX1(); + int y = p.left().y() + box.getY1(); + int z = p.left().z() + box.getZ1(); + + place(worldView, feature, x, y, z, p.right()); + } + } + + for (WorldgenFeature branch : feature.branches) { + generate(rng, branch, worldView); + } + } + + protected boolean shouldGenerate(WorldView worldView, WorldgenFeature feature) { + return true; + } + + protected void place(WorldView worldView, WorldgenFeature feature, int blockX, int blockY, int blockZ, ImmutableBlockMeta bm) { + worldView.setBlock(blockX, blockY, blockZ, bm); + } + + protected abstract int getSeedX(TSeed seed); + protected abstract int getSeedY(TSeed seed); + protected abstract int getSeedZ(TSeed seed); + + /** Gets a seeds recursions/branches, if any. */ + protected Collection getSeedBranches(TSeed seed) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java new file mode 100644 index 00000000..ba497a5f --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java @@ -0,0 +1,96 @@ +package com.cardinalstar.cubicchunks.world.worldgen; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.XSTR; +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; +import com.cardinalstar.cubicchunks.world.cube.blockview.IMutableBlockView; +import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class MapGenCaveFluids implements ICubicPopulator { + + private static final ForgeDirection[] BLOCK_MASK = { + ForgeDirection.NORTH, + ForgeDirection.SOUTH, + ForgeDirection.EAST, + ForgeDirection.WEST + }; + + private final XSTR rng = new XSTR(); + + private final ImmutableBlockMeta fluid; + + private final int chances; + + public MapGenCaveFluids(ImmutableBlockMeta fluid) { + this(fluid, 10); + } + + public MapGenCaveFluids(ImmutableBlockMeta fluid, int chances) { + this.fluid = fluid; + this.chances = chances; + } + + @Override + public void generate(World world, CubePos pos) { + if (pos.getY() >= 0) return; + + IMutableBlockView cubeView = new CubeBlockView((Cube) ((ICubicWorld) world).getCubeFromCubeCoords(pos)); + + long seed = Fnv1a64.initialState(); + + seed = Fnv1a64.hashStep(seed, world.getSeed()); + seed = Fnv1a64.hashStep(seed, pos.getX()); + seed = Fnv1a64.hashStep(seed, pos.getY()); + seed = Fnv1a64.hashStep(seed, pos.getZ()); + + rng.setSeed(seed); + + outer: for (int i = 0; i < chances; i++) { + int x = rng.nextInt(14) + 1; + int y = rng.nextInt(14) + 1; + int z = rng.nextInt(14) + 1; + + if (cubeView.getBlock(x, y, z) != Blocks.stone) continue; + if (cubeView.getBlock(x, y - 1, z) != Blocks.stone) continue; + if (cubeView.getBlock(x, y + 1, z) != Blocks.stone) continue; + + int valid = 0; + + for (ForgeDirection dir : BLOCK_MASK) { + Block block = cubeView.getBlock(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ); + + if (block == Blocks.stone) { + valid++; + } else if (block != Blocks.air) { + continue outer; + } + } + + if (valid != 3) continue; + + cubeView.setBlock(x, y, z, fluid); + + int globalX = x + pos.getX() * 16; + int globalY = y + pos.getY() * 16; + int globalZ = z + pos.getZ() * 16; + + fluid.getBlock().onNeighborBlockChange(world, globalX, globalY, globalZ, Blocks.air); + + world.scheduledUpdatesAreImmediate = true; + fluid.getBlock().updateTick(world, globalX, globalY, globalZ, world.rand); + world.scheduledUpdatesAreImmediate = false; + + break; + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java index 04754603..d9bbfa6c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java @@ -1,33 +1,23 @@ package com.cardinalstar.cubicchunks.world.worldgen; import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; +import java.util.Collection; import java.util.List; import java.util.Random; -import javax.annotation.Nonnull; - import net.minecraft.block.Block; import net.minecraft.init.Blocks; import net.minecraft.util.MathHelper; import net.minecraft.world.biome.BiomeGenBase; -import org.joml.Vector3i; -import org.joml.Vector3ic; - -import com.cardinalstar.cubicchunks.api.XYZAddressable; -import com.cardinalstar.cubicchunks.api.XYZMap; import com.cardinalstar.cubicchunks.api.util.Box; -import com.cardinalstar.cubicchunks.util.Coords; -import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.util.XSTR; import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; -import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; -import it.unimi.dsi.fastutil.longs.LongIterator; -import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; +import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; -public class MapGenCavesCubic extends SeedBasedCubicPopulator { +public class MapGenCavesCubic extends FeatureCubicGenerator { private final XSTR caveRandom = new XSTR(0); @@ -36,7 +26,7 @@ public MapGenCavesCubic() { } @Override - protected void getSeeds(Random rng, int cubeX, int cubeY, int cubeZ, List seeds) { + protected void getSeedsImpl(Random rng, int cubeX, int cubeY, int cubeZ, List seeds) { if (rng.nextInt(4) != 0) return; double offsetX = cubeX * 16 + rng.nextInt(16); @@ -65,28 +55,30 @@ protected void getSeeds(Random rng, int cubeX, int cubeY, int cubeZ, List getSeedBranches(CaveSeed caveSeed) { + return caveSeed.branches; + } - digBlock(worldView, x, y, z); - } - } + @Override + protected boolean shouldGenerate(WorldView worldView, WorldgenFeature feature) { + Box box = worldView.getBounds(); - for (CaveSeed branch : caveSeed.branches) { - generateCave(branch, worldView); - } + return !scanOuterBoxForWater(worldView, box.getX1(), box.getX2(), box.getY1(), box.getY2(), box.getZ1(), box.getZ2()); } private static boolean scanOuterBoxForWater(WorldView worldView, int xmin, int xmax, int zmin, int zmax, int ymax, int ymin) { @@ -113,8 +105,7 @@ private static boolean scanOuterBoxForWater(WorldView worldView, int xmin, int x // If we've just scanned the top block (y == ymax + 1) and we aren't scanning the walls, then jump // to the bottom (y == ymin) - if (y != ymin - 1 && x != xmin && x != xmax - 1 && z != zmin && z != zmax - 1) - { + if (y != ymin - 1 && x != xmin && x != xmax - 1 && z != zmin && z != zmax - 1) { y = ymin; } } @@ -132,39 +123,23 @@ private boolean isExceptionBiome(BiomeGenBase biome) return biome == BiomeGenBase.desert; } - /** - * Digs out the current block, default implementation removes stone, filler, and top block - * Sets the block to lava if y is less then 10, and air other wise. - * If setting to air, it also checks to see if we've broken the surface and if so - * tries to make the floor the biome's top block - * - * @param worldView The world view - * @param x global X position - * @param y global Y position - * @param z global Z position - */ - protected void digBlock(WorldView worldView, int x, int y, int z) - { + @Override + protected void place(WorldView worldView, WorldgenFeature feature, int x, int y, int z, ImmutableBlockMeta bm) { BiomeGenBase biome = worldView.getBiomeGenForBlock(x, y, z); Block block = worldView.getBlock(x, y, z); Block top = (isExceptionBiome(biome) ? Blocks.grass : biome.topBlock); Block filler = (isExceptionBiome(biome) ? Blocks.dirt : biome.fillerBlock); - if (block == Blocks.stone || block == Blocks.bedrock || block == filler || block == top) - { - if (false) - { - worldView.setBlock(x, y, z, Blocks.lava, 0); - } - else - { - worldView.setBlock(x, y, z, Blocks.air, 0); - } + if (block == Blocks.stone || block == Blocks.bedrock || block == filler || block == top) { + worldView.setBlock(x, y, z, bm); } } - protected void walkCave(CaveSeed cave) { + private static final LazyBlock AIR = new LazyBlock(Mods.Minecraft, () -> Blocks.air, 0); + + @Override + protected void generateSeed(CaveSeed cave, WorldgenFeature feature) { long seed = cave.seed; double offsetX = cave.offsetX; double offsetY = cave.offsetY; @@ -263,7 +238,7 @@ protected void walkCave(CaveSeed cave) { if (ellipseY > -0.7D && ellipseX * ellipseX + ellipseY * ellipseY + ellipseZ * ellipseZ < 1.0D) { - cave.dig(globalX, globalY, globalZ); + feature.setBlock(globalX, globalY, globalZ, AIR); } } } @@ -278,7 +253,7 @@ protected void walkCave(CaveSeed cave) { } } - protected class CaveSeed { + protected static class CaveSeed { private final long seed; private final double offsetX; private final double offsetY; @@ -290,10 +265,6 @@ protected class CaveSeed { private final int stepCount; private final double height; - private final XYZMap pendingDigs = new XYZMap<>(); - - private final Box.Mutable aabb; - private final List branches = new ArrayList<>(); public CaveSeed(long seed, double offsetX, double offsetY, double offsetZ, float caveLength, float marchYaw, float marchPitch, int stepIndex, int stepCount, double height) { @@ -307,90 +278,6 @@ public CaveSeed(long seed, double offsetX, double offsetY, double offsetZ, float this.stepIndex = stepIndex; this.stepCount = stepCount; this.height = height; - - aabb = new Box.Mutable((int) offsetX, (int) offsetY, (int) offsetZ, (int) offsetX, (int) offsetY, (int) offsetZ); - - walkCave(this); - } - - public void dig(int x, int y, int z) { - aabb.expand(x, y, z); - - DigSet digs = pendingDigs.get(Coords.blockToCube(x), Coords.blockToCube(y), Coords.blockToCube(z)); - - if (digs == null) { - pendingDigs.put(digs = new DigSet(Coords.blockToCube(x), Coords.blockToCube(y), Coords.blockToCube(z))); - } - - digs.dig(Coords.blockToLocal(x), Coords.blockToLocal(y), Coords.blockToLocal(z)); - } - - public boolean affects(int cubeX, int cubeY, int cubeZ) { - return aabb.containsCube(cubeX, cubeY, cubeZ); - } - - public Iterable getDigs(int cubeX, int cubeY, int cubeZ) { - DigSet digs = pendingDigs.get(cubeX, cubeY, cubeZ); - - return digs == null ? Collections.emptyList() : digs; - } - } - - protected static class DigSet implements Iterable, XYZAddressable { - - private final int x, y, z; - - private final LongLinkedOpenHashSet digs = new LongLinkedOpenHashSet(); - - public DigSet(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override - public int getX() { - return x; - } - - @Override - public int getY() { - return y; - } - - @Override - public int getZ() { - return z; - } - - public void dig(int x, int y, int z) { - digs.add(CoordinatePacker.pack(x, y, z)); - } - - @Override - @Nonnull - public Iterator iterator() { - return new Iterator<>() { - - private final LongIterator iter = digs.iterator(); - private final Vector3i v = new Vector3i(); - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public Vector3ic next() { - long k = iter.nextLong(); - - v.x = CoordinatePacker.unpackX(k); - v.y = CoordinatePacker.unpackY(k); - v.z = CoordinatePacker.unpackZ(k); - - return v; - } - }; } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicPopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java similarity index 98% rename from src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicPopulator.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java index 7bfaed26..04e12f7d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicPopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java @@ -18,7 +18,7 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; -public abstract class SeedBasedCubicPopulator implements ICubeTerrainGenerator { +public abstract class SeedBasedCubicGenerator implements ICubeTerrainGenerator { private final XSTR rng = new XSTR(0); @@ -46,7 +46,7 @@ public abstract class SeedBasedCubicPopulator Blocks.water); + private static final LazyBlock LAVA_STILL = new LazyBlock(Mods.Minecraft, () -> Blocks.lava); + + public static void init() { + initVanilla(); + + if (Mods.EtFuturumRequiem.isModLoaded()) { + initEFR(); + } + } + + private static void initVanilla() { + CubeGeneratorsRegistry.registerVanillaGenerator("caves", new MapGenCavesCubic()); + + CubeGeneratorsRegistry.registerVanillaPopulator( + "water-spouts", + new MapGenCaveFluids(WATER_STILL)); + + CubeGeneratorsRegistry.registerVanillaPopulator( + "lava-spouts", + new MapGenCaveFluids(LAVA_STILL)); + } + + @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) + private static void initEFR() { + CubeGeneratorsRegistry.registerVanillaPopulator("low-deepslate", new DeepslateCubePopulator(), "water-spouts", "lava-spouts"); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java new file mode 100644 index 00000000..79c7c973 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java @@ -0,0 +1,143 @@ +package com.cardinalstar.cubicchunks.world.worldgen; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nonnull; + +import org.joml.Vector3i; +import org.joml.Vector3ic; + +import com.cardinalstar.cubicchunks.api.XYZAddressable; +import com.cardinalstar.cubicchunks.api.XYZMap; +import com.cardinalstar.cubicchunks.api.util.Box; +import com.cardinalstar.cubicchunks.util.Coords; +import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; + +public class WorldgenFeature { + + private final TSeed seed; + + private final XYZMap pendingOps = new XYZMap<>(); + + private final Box.Mutable aabb; + + public final List> branches = new ArrayList<>(); + + public WorldgenFeature(int centerX, int centerY, int centerZ, TSeed seed) { + this.seed = seed; + + centerX = Coords.blockToCube(centerX); + centerY = Coords.blockToCube(centerY); + centerZ = Coords.blockToCube(centerZ); + + aabb = new Box.Mutable(centerX, centerY, centerZ, centerX, centerY, centerZ); + } + + public TSeed getSeed() { + return seed; + } + + public void setBlock(int blockX, int blockY, int blockZ, ImmutableBlockMeta bm) { + aabb.expand(Coords.blockToLocal(blockX), Coords.blockToLocal(blockY), Coords.blockToLocal(blockZ)); + + BlockOperationSet ops = pendingOps.get( + Coords.blockToCube(blockX), + Coords.blockToCube(blockY), + Coords.blockToCube(blockZ)); + + if (ops == null) { + pendingOps.put(ops = new BlockOperationSet( + Coords.blockToCube(blockX), + Coords.blockToCube(blockY), + Coords.blockToCube(blockZ))); + } + + ops.setBlock(Coords.blockToLocal(blockX), Coords.blockToLocal(blockY), Coords.blockToLocal(blockZ), bm); + } + + public boolean affects(int cubeX, int cubeY, int cubeZ) { + return aabb.contains(cubeX, cubeY, cubeZ); + } + + public Iterable> getOperations(int cubeX, int cubeY, int cubeZ) { + BlockOperationSet ops = pendingOps.get(cubeX, cubeY, cubeZ); + + return ops == null ? Collections.emptyList() : ops; + } + + protected static class BlockOperationSet implements Iterable>, XYZAddressable { + + private final int x, y, z; + + private final LongLinkedOpenHashSet coords = new LongLinkedOpenHashSet(); + private final ObjectArrayList blocks = new ObjectArrayList<>(); + + public BlockOperationSet(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + + @Override + public int getZ() { + return z; + } + + public void setBlock(int x, int y, int z, ImmutableBlockMeta bm) { + if (coords.add(CoordinatePacker.pack(x, y, z))) { + blocks.add(bm); + } + } + + @Override + @Nonnull + public Iterator> iterator() { + return new Iterator<>() { + + private final LongIterator coordIter = coords.iterator(); + private final ObjectIterator blockIter = blocks.iterator(); + + private final Vector3i v = new Vector3i(); + private final ObjectObjectMutablePair pair = ObjectObjectMutablePair.of(v, null); + + @Override + public boolean hasNext() { + return coordIter.hasNext() && blockIter.hasNext(); + } + + @Override + public Pair next() { + long k = coordIter.nextLong(); + + v.x = CoordinatePacker.unpackX(k); + v.y = CoordinatePacker.unpackY(k); + v.z = CoordinatePacker.unpackZ(k); + + pair.right(blockIter.next()); + + return pair; + } + }; + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/modcompat/efr/DeepslateCubePopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java similarity index 96% rename from src/main/java/com/cardinalstar/cubicchunks/modcompat/efr/DeepslateCubePopulator.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java index 2bc19e13..ef403c8b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/modcompat/efr/DeepslateCubePopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java @@ -1,4 +1,4 @@ -package com.cardinalstar.cubicchunks.modcompat.efr; +package com.cardinalstar.cubicchunks.world.worldgen.compat; import net.minecraft.block.Block; import net.minecraft.init.Blocks; diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java index b050a0f7..ca448219 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java @@ -131,6 +131,8 @@ private FillerInfo getBottomFillerInfo() { Chunk chunk = vanilla.provideChunk(0, 0); + ((IColumnInternal) chunk).setColumn(false); + bottom = analyzeBottomFiller(new ChunkBlockView(chunk)); return bottom; @@ -162,6 +164,8 @@ private FillerInfo getTopFillerInfo() { Chunk chunk = vanilla.provideChunk(0, 0); + ((IColumnInternal) chunk).setColumn(false); + top = analyzeTopFiller(new ChunkBlockView(chunk)); return top; @@ -311,6 +315,8 @@ private void generateVanillaChunk(int cubeX, int cubeZ, Random rand) { lastChunk = vanilla.provideChunk(cubeX, cubeZ); + ((IColumnInternal) lastChunk).setColumn(false); + lastChunkView = new ChunkBlockView(lastChunk); removeBedrock(lastChunkView, rand); @@ -394,7 +400,7 @@ public void populate(Cube cube) { WorldgenHangWatchdog.startWorldGen(); - CubeGeneratorsRegistry.populateVanillaCubic(world, cube); + CubeGeneratorsRegistry.populateVanillaCube(world, cube.getCoords()); Cube withinVanillaChunk = cube; From ab1db58d90f18df0818e760773a14134f9885fed Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 24 Oct 2025 14:05:45 -0400 Subject: [PATCH 02/22] CoordinatePacker mixin --- .../cardinalstar/cubicchunks/api/XYZMap.java | 11 ++--- .../cubicchunks/mixin/Mixins.java | 13 ++++- .../early/common/MixinCoordinatePacker.java | 47 +++++++++++++++++++ .../cardinalstar/cubicchunks/util/Coords.java | 20 ++++++++ .../world/worldgen/FastCubePosMap.java | 33 +++---------- 5 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCoordinatePacker.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java index 2686e610..5e33db83 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java @@ -25,6 +25,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import com.cardinalstar.cubicchunks.util.Coords; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; /** @@ -40,16 +41,12 @@ public class XYZMap implements Iterable { Long2ObjectOpenHashMap items = new Long2ObjectOpenHashMap<>(); - private long key(long x, long y, long z) { - return ((x & 0x1FFFFF) << 42) | ((y & 0x1FFFFF) << 21) | (z & 0x1FFFFF); - } - public T remove(int x, int y, int z) { - return items.remove(key(x, y, z)); + return items.remove(Coords.key(x, y, z)); } public final T get(int x, int y, int z) { - return items.get(key(x, y, z)); + return items.get(Coords.key(x, y, z)); } public final T get(XYZAddressable xyz) { @@ -57,7 +54,7 @@ public final T get(XYZAddressable xyz) { } public final void put(T item) { - items.put(key(item.getX(), item.getY(), item.getZ()), item); + items.put(Coords.key(item.getX(), item.getY(), item.getZ()), item); } public final void remove(T item) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index 211d4e1e..a1a1b7a0 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -261,7 +261,18 @@ public enum Mixins implements IMixins { new MixinBuilder("Changing default level in servers with this mod installed to be VanillaCubic.") .addServerMixins("server.MixinDedicatedServer_DefaultLevelType") .setPhase(Phase.EARLY) - .setApplyIf(() -> true)); + .setApplyIf(() -> true)), + + // ============================================================= + // Mod Mixins + // ============================================================= + MIXIN_COORD_PACKER(new MixinBuilder("Overwrite GTNHLib CoordinatePacker algorithm with a CC-compatible one") + .addCommonMixins("common.MixinCoordinatePacker") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), + + // + ; private final MixinBuilder builder; diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCoordinatePacker.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCoordinatePacker.java new file mode 100644 index 00000000..79c911e4 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCoordinatePacker.java @@ -0,0 +1,47 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +import com.cardinalstar.cubicchunks.util.Coords; +import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; + +@Mixin(value = CoordinatePacker.class, remap = false) +public class MixinCoordinatePacker { + + /** + * @author Recursive Pineapple + * @reason Performance + */ + @Overwrite + public static long pack(int x, int y, int z) { + return Coords.key(x, y, z); + } + + /** + * @author Recursive Pineapple + * @reason Performance + */ + @Overwrite + public static int unpackX(long packed) { + return Coords.x(packed); + } + + /** + * @author Recursive Pineapple + * @reason Performance + */ + @Overwrite + public static int unpackY(long packed) { + return Coords.y(packed); + } + + /** + * @author Recursive Pineapple + * @reason Performance + */ + @Overwrite + public static int unpackZ(long packed) { + return Coords.z(packed); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java index 660d9721..17d02214 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java @@ -154,4 +154,24 @@ public static long coordsSeedHash(long seed, int x, int y, int z) { public static Random coordsSeedRandom(long seed, int x, int y, int z) { return new Random(coordsSeedHash(seed, x, y, z)); } + + public static final int MASK = 0x1FFFFF; + public static final int SHIFT = 21; + + public static long key(long x, long y, long z) { + return ((x & MASK) << SHIFT << SHIFT) | ((y & MASK) << SHIFT) | (z & MASK); + } + + public static int x(long key) { + return (int) ((key >> SHIFT >> SHIFT) & MASK); + } + + public static int y(long key) { + return (int) ((key >> SHIFT) & MASK); + } + + public static int z(long key) { + return (int) (key & MASK); + } + } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java index 86de5b03..3d2bf3b1 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java @@ -8,52 +8,33 @@ import javax.annotation.Nonnull; import com.cardinalstar.cubicchunks.api.XYZAddressable; +import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; - import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; public class FastCubePosMap extends AbstractMap { - public static final int MASK = 0x1FFFFF; - public static final int SHIFT = 21; - - public static long key(long x, long y, long z) { - return ((x & MASK) << SHIFT << SHIFT) | ((y & MASK) << SHIFT) | (z & MASK); - } - - private static int x(long key) { - return (int) ((key >> SHIFT >> SHIFT) & MASK); - } - - private static int y(long key) { - return (int) ((key >> SHIFT) & MASK); - } - - private static int z(long key) { - return (int) (key & MASK); - } - public final Long2ObjectOpenHashMap map = new Long2ObjectOpenHashMap<>(); @Override public V get(Object key) { XYZAddressable pos = (XYZAddressable) key; - return map.get(key(pos.getX(), pos.getY(), pos.getZ())); + return map.get(Coords.key(pos.getX(), pos.getY(), pos.getZ())); } @Override public V remove(Object key) { XYZAddressable pos = (XYZAddressable) key; - return map.remove(key(pos.getX(), pos.getY(), pos.getZ())); + return map.remove(Coords.key(pos.getX(), pos.getY(), pos.getZ())); } @Override public V put(CubePos key, V value) { - return map.put(key(key.getX(), key.getY(), key.getZ()), value); + return map.put(Coords.key(key.getX(), key.getY(), key.getZ()), value); } private class FastEntry implements Entry { @@ -101,9 +82,9 @@ public Entry next() { var e = iter.next(); entry.entry = e; - entry.pos.x = x(e.getLongKey()); - entry.pos.y = y(e.getLongKey()); - entry.pos.z = z(e.getLongKey()); + entry.pos.x = Coords.x(e.getLongKey()); + entry.pos.y = Coords.y(e.getLongKey()); + entry.pos.z = Coords.z(e.getLongKey()); return entry; } From 2ce21a8817008e5d2eaf9843cb30e411ca65c23e Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 24 Oct 2025 14:06:04 -0400 Subject: [PATCH 03/22] Prevent LightingManager NPE --- .../cubicchunks/mixin/early/client/MixinWorldClient.java | 2 +- .../cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java index d5ea94ee..21720c49 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java @@ -62,7 +62,7 @@ public void initCubicWorldClient(IntRange heightRange, IntRange generationRange) @Override public void tickCubicWorld() { - getLightingManager().onTick(); + if (getLightingManager() != null) getLightingManager().onTick(); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java index 73a3f7ba..05e0d0d5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java @@ -422,7 +422,7 @@ public int getHeight() { @Inject(method = "updateLightByType", at = @At("HEAD"), cancellable = true) private void updateLightByType(EnumSkyBlock lightType, int x, int y, int z, CallbackInfoReturnable ci) { - ci.setReturnValue(getLightingManager().checkLightFor(lightType, x, y, z)); + ci.setReturnValue(getLightingManager() != null && getLightingManager().checkLightFor(lightType, x, y, z)); } /** From c2abab5aa16c6181b638200c9dc6a63fbb95ee91 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 24 Oct 2025 14:06:32 -0400 Subject: [PATCH 04/22] Cleanup --- .../java/com/cardinalstar/cubicchunks/CubicChunks.java | 9 --------- .../network/PacketEncoderCubeBlockChange.java | 1 - .../java/com/cardinalstar/cubicchunks/util/XSTR.java | 3 +-- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index 3128a6a6..f4076c1e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -54,8 +54,6 @@ import com.cardinalstar.cubicchunks.worldgen.WorldgenHangWatchdog; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; - -import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; @@ -299,11 +297,4 @@ public static void bigWarning(String format, Object... data) { } LOGGER.log(Level.WARN, "****************************************"); } - - public static boolean hasOptifine() { - return SideUtils.getForSide( - () -> () -> FMLClientHandler.instance() - .hasOptifine(), - () -> () -> false); - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index 0e3cc4cf..ba87eb91 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -36,7 +36,6 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; - import gnu.trove.TShortCollection; import gnu.trove.iterator.TIntIterator; import gnu.trove.set.TIntSet; diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java b/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java index 04a9f3e3..596e9473 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java @@ -32,7 +32,6 @@ public class XSTR extends Random { private static final long serialVersionUID = 6208727693524452904L; private long seed; - private long last; private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << 24) private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); @@ -200,7 +199,7 @@ public synchronized double nextGaussian() { */ @Override public int nextInt(int bound) { - last = seed ^ (seed << 21); + long last = seed ^ (seed << 21); last ^= (last >>> 35); last ^= (last << 4); seed = last; From 0ff06b33fe301f2dbfc1c03c7f55cb74312dcb1e Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 24 Oct 2025 14:06:51 -0400 Subject: [PATCH 05/22] Angelica interop --- .../api/compat/CubicChunksVideoSettings.java | 23 ++++++ .../event/handlers/ClientEventHandler.java | 80 ++++++------------- .../modcompat/angelica/AngelicaInterop.java | 18 +++++ .../modcompat/angelica/IAngelicaDelegate.java | 11 +++ .../network/PacketEncoderColumn.java | 5 ++ .../network/PacketEncoderCubes.java | 15 ++-- .../network/PacketEncoderUnloadColumn.java | 5 ++ .../network/PacketEncoderUnloadCube.java | 5 ++ 8 files changed, 101 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/compat/CubicChunksVideoSettings.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/AngelicaInterop.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/compat/CubicChunksVideoSettings.java b/src/main/java/com/cardinalstar/cubicchunks/api/compat/CubicChunksVideoSettings.java new file mode 100644 index 00000000..cd4231a6 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/compat/CubicChunksVideoSettings.java @@ -0,0 +1,23 @@ +package com.cardinalstar.cubicchunks.api.compat; + +import com.cardinalstar.cubicchunks.CubicChunksConfig; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; + +public class CubicChunksVideoSettings { + + public static int getMinVerticalViewDistance() { + return 2; + } + + public static int getMaxVerticalViewDistance() { + return AngelicaInterop.hasDelegate() ? 64 : 32; + } + + public static int getVerticalViewDistance() { + return CubicChunksConfig.verticalCubeLoadDistance; + } + + public static void setVerticalViewDistance(int distance) { + CubicChunksConfig.setVerticalViewDistance(distance); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java index 8875432b..52f698c2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java @@ -41,16 +41,16 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.CubicChunksConfig; +import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings; import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; import com.cardinalstar.cubicchunks.api.worldgen.VanillaCompatibilityGeneratorProviderBase; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.mixin.early.client.IGuiCreateWorld; import com.cardinalstar.cubicchunks.mixin.early.client.IGuiOptionsRowList; -import com.cardinalstar.cubicchunks.mixin.early.client.IGuiScreen; import com.cardinalstar.cubicchunks.mixin.early.client.IGuiVideoSettings; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.server.ICubicPlayerList; import com.cardinalstar.cubicchunks.util.MathUtil; - import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -91,52 +91,12 @@ public void onServerTick(TickEvent.ServerTickEvent event) { public void initGuiEvent(InitGuiEvent.Post event) { GuiScreen currentGui = event.gui; - if (currentGui instanceof GuiVideoSettings) { + if (currentGui instanceof GuiVideoSettings && !AngelicaInterop.hasDelegate()) { GuiVideoSettings gvs = (GuiVideoSettings) currentGui; - if (!FMLClientHandler.instance() - .hasOptifine()) { - IGuiOptionsRowList gowl = (IGuiOptionsRowList) ((IGuiVideoSettings) gvs).getOptionsRowList(); - GuiOptionsRowList.Row row = this.createRow(100, gvs.width); - gowl.getOptions() - .add(1, row); - } else { - int idx = 3; - int btnSpacing = 20; - ((IGuiScreen) gvs).getButtonList() - .add( - idx, - new VertViewDistanceSlider( - 100, - gvs.width / 2 - 155 + 160, - gvs.height / 6 + btnSpacing * (idx / 2) - 12)); - List buttons = ((IGuiScreen) gvs).getButtonList(); - // reposition all buttons except the last 4 (last 3 and done) - for (int i = 0; i < buttons.size() - 4; i++) { - GuiButton btn = buttons.get(i); - int x = gvs.width / 2 - 155 + i % 2 * 160; - int y = gvs.height / 6 + 21 * (i / 2) - 12; - btn.xPosition = x; - btn.yPosition = y; - } - // now position the last 3 buttons excluding "done" to be 3-in-a-row - for (int i = buttons.size() - 4; i < buttons.size() - 1; i++) { - GuiButton btn = buttons.get(i); - - int newBtnWidth = 150 * 2 / 3; - int minX = gvs.width / 2 - 155; - int maxX = gvs.width / 2 - 155 + 160 + btn.width; - - int minXCenter = minX + newBtnWidth / 2; - int maxXCenter = maxX - newBtnWidth / 2; - - int x = minXCenter + (i % 3) * (maxXCenter - minXCenter) / 2 - newBtnWidth / 2; - int y = gvs.height / 6 + 21 * (buttons.size() - 4) / 2 - 12; - - btn.xPosition = x; - btn.yPosition = y; - btn.width = newBtnWidth; - } - } + IGuiOptionsRowList gowl = (IGuiOptionsRowList) ((IGuiVideoSettings) gvs).getOptionsRowList(); + GuiOptionsRowList.Row row = this.createRow(100, gvs.width); + gowl.getOptions() + .add(1, row); } } @@ -147,13 +107,14 @@ private GuiOptionsRowList.Row createRow(int buttonId, int width) { private class VertViewDistanceSlider extends GuiButton { - private final int MAX_VIEW_DIST = CubicChunks.hasOptifine() ? 64 : 32; + private final int minViewDist = CubicChunksVideoSettings.getMinVerticalViewDistance(); + private final int maxViewDist = CubicChunksVideoSettings.getMaxVerticalViewDistance(); private float sliderValue; public boolean dragging; public VertViewDistanceSlider(int buttonId, int x, int y) { super(buttonId, x, y, 150, 20, ""); - this.sliderValue = MathUtil.unlerp(CubicChunksConfig.verticalCubeLoadDistance, 2, MAX_VIEW_DIST); + this.sliderValue = getSliderValue(); this.displayString = this.createDisplayString(); } @@ -176,9 +137,8 @@ protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) { if (this.dragging) { this.sliderValue = (float) (mouseX - (this.xPosition + 4)) / (float) (this.width - 8); this.sliderValue = MathHelper.clamp_float(this.sliderValue, 0.0F, 1.0F); - CubicChunksConfig - .setVerticalViewDistance(Math.round(MathUtil.lerp(this.sliderValue, 2, MAX_VIEW_DIST))); - this.sliderValue = MathUtil.unlerp(CubicChunksConfig.verticalCubeLoadDistance, 2, MAX_VIEW_DIST); + onSliderValueChanged(); + this.sliderValue = getSliderValue(); this.displayString = this.createDisplayString(); } @@ -211,9 +171,8 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { if (super.mousePressed(mc, mouseX, mouseY)) { this.sliderValue = (float) (mouseX - (this.xPosition + 4)) / (float) (this.width - 8); this.sliderValue = MathHelper.clamp_float(this.sliderValue, 0.0F, 1.0F); - CubicChunksConfig - .setVerticalViewDistance(Math.round(MathUtil.lerp(this.sliderValue, 2, MAX_VIEW_DIST))); - this.sliderValue = MathUtil.unlerp(CubicChunksConfig.verticalCubeLoadDistance, 2, MAX_VIEW_DIST); + onSliderValueChanged(); + this.sliderValue = getSliderValue(); this.displayString = this.createDisplayString(); this.dragging = true; return true; @@ -222,10 +181,19 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { } } + private void onSliderValueChanged() { + CubicChunksVideoSettings + .setVerticalViewDistance(Math.round(MathUtil.lerp(this.sliderValue, minViewDist, maxViewDist))); + } + + private float getSliderValue() { + return MathUtil.unlerp(CubicChunksVideoSettings.getVerticalViewDistance(), minViewDist, maxViewDist); + } + private String createDisplayString() { return I18n.format( CubicChunks.MODID + ".gui.vertical_cube_load_distance", - CubicChunksConfig.verticalCubeLoadDistance); + CubicChunksVideoSettings.getVerticalViewDistance()); } /** diff --git a/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/AngelicaInterop.java b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/AngelicaInterop.java new file mode 100644 index 00000000..713d279f --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/AngelicaInterop.java @@ -0,0 +1,18 @@ +package com.cardinalstar.cubicchunks.modcompat.angelica; + +public class AngelicaInterop { + + private static IAngelicaDelegate delegate; + + public static boolean hasDelegate() { + return delegate != null; + } + + public static IAngelicaDelegate getDelegate() { + return delegate; + } + + public static void setDelegate(IAngelicaDelegate delegate) { + AngelicaInterop.delegate = delegate; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java new file mode 100644 index 00000000..9a6ed2ea --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java @@ -0,0 +1,11 @@ +package com.cardinalstar.cubicchunks.modcompat.angelica; + +public interface IAngelicaDelegate { + + void onColumnLoaded(int chunkX, int chunkZ); + void onColumnUnloaded(int chunkX, int chunkZ); + + void onCubeLoaded(int cubeX, int cubeY, int cubeZ); + void onCubeUnloaded(int cubeX, int cubeY, int cubeZ); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java index 673bddc6..17860568 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java @@ -26,6 +26,7 @@ import net.minecraft.world.chunk.Chunk; import com.cardinalstar.cubicchunks.client.CubeProviderClient; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.github.bsideup.jabel.Desugar; @@ -83,5 +84,9 @@ public void process(World world, PacketColumn packet) { ByteBuf buf = Unpooled.wrappedBuffer(packet.data); WorldEncoder.decodeColumn(new CCPacketBuffer(buf), column); + + if (AngelicaInterop.hasDelegate()) { + AngelicaInterop.getDelegate().onColumnLoaded(packet.chunkX, packet.chunkZ); + } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index a82d595e..1ce6488c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Objects; import javax.annotation.ParametersAreNonnullByDefault; @@ -34,10 +33,10 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.client.CubeProviderClient; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; - import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -146,9 +145,15 @@ public void process(World world, PacketCubes packet) { ByteBuf buf = Unpooled.wrappedBuffer(packet.data); WorldEncoder.decodeCube(new CCPacketBuffer(buf), cubes); - cubes.stream() - .filter(Objects::nonNull) - .forEach(Cube::markForRenderUpdate); + for (Cube cube : cubes) { + if (cube != null) { + cube.markForRenderUpdate(); + + if (AngelicaInterop.hasDelegate()) { + AngelicaInterop.getDelegate().onCubeLoaded(cube.getX(), cube.getY(), cube.getZ()); + } + } + } packet.tileEntityTags.forEach(list -> { list.forEach(tag -> { diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java index bff4414f..fdfd333e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java @@ -25,6 +25,7 @@ import net.minecraft.world.World; import com.cardinalstar.cubicchunks.client.CubeProviderClient; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.github.bsideup.jabel.Desugar; @@ -68,5 +69,9 @@ public void process(World world, PacketUnloadColumn packet) { CubeProviderClient cubeCache = (CubeProviderClient) worldClient.getCubeCache(); cubeCache.unloadChunk(packet.chunkX, packet.chunkZ); + + if (AngelicaInterop.hasDelegate()) { + AngelicaInterop.getDelegate().onColumnUnloaded(packet.chunkX, packet.chunkZ); + } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java index 9e7bcf47..c4a7e78e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java @@ -25,6 +25,7 @@ import net.minecraft.world.World; import com.cardinalstar.cubicchunks.client.CubeProviderClient; +import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.github.bsideup.jabel.Desugar; @@ -71,5 +72,9 @@ public void process(World world, PacketUnloadCube packet) { cubeCache.getCube(packet.pos) .markForRenderUpdate(); cubeCache.unloadCube(packet.pos); + + if (AngelicaInterop.hasDelegate()) { + AngelicaInterop.getDelegate().onCubeLoaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); + } } } From ac05363e089ef212ce1133fd045b1c237bb57579 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 14 Nov 2025 12:01:19 -0500 Subject: [PATCH 06/22] Lots of changes --- dependencies.gradle | 10 +- repositories.gradle | 2 +- .../cardinalstar/cubicchunks/CubicChunks.java | 75 ++- .../cubicchunks/CubicChunksConfig.java | 48 +- .../cardinalstar/cubicchunks/api/ICube.java | 22 +- .../api/registry/AbstractRegistryEntry.java | 38 ++ .../api/registry/IRegistryEntry.java | 22 + .../registry/Registry.java} | 13 +- .../cubicchunks/api/util/Box.java | 16 +- .../api/world/ICubicWorldType.java | 10 +- .../cubicchunks/api/world/Precalculable.java | 8 + ...derBase.java => StorageFormatFactory.java} | 50 +- ...gData.java => BuiltinWorldDecorators.java} | 58 +-- .../api/worldgen/CubeGeneratorsRegistry.java | 157 ------ .../api/worldgen/DependencyRegistry.java | 13 + .../api/worldgen/GenerationResult.java | 27 ++ ...ubeGenerator.java => IWorldGenerator.java} | 112 +---- ...llaCompatibilityGeneratorProviderBase.java | 65 --- .../api/worldgen/WorldgenRegistry.java | 14 + .../worldgen/decoration/ICubeGenerator.java | 21 + .../worldgen/decoration/ICubePopulator.java} | 48 +- .../worldgen/decoration/IWorldDecorator.java | 29 ++ .../impl/StandardDependencyRegistry.java | 56 +++ .../worldgen/impl/StandardWorldDecorator.java | 56 +++ .../populator/ICubeTerrainGenerator.java | 17 - .../worldgen/populator/ICubicPopulator.java | 57 --- .../worldtype}/VanillaCubicWorldType.java | 25 +- .../cubicchunks/async/TaskPool.java | 174 +++++++ .../client/CubeProviderClient.java | 23 +- .../cubicchunks/client/WorldDiagnostics.java | 38 ++ .../cubicchunks/event/events/ColumnEvent.java | 26 + .../cubicchunks/event/events/CubeEvent.java | 52 +- .../event/handlers/ClientEventHandler.java | 121 ----- .../cubicchunks/mixin/Mixins.java | 12 + .../early/client/MixinEntityRenderer.java | 45 +- .../mixin/early/client/MixinWorldClient.java | 2 - .../mixin/early/common/MixinChunk_Cubes.java | 7 +- .../early/common/MixinEntity_Brightness.java | 21 + .../common/MixinExtendedBlockStorage.java | 27 ++ .../mixin/early/common/MixinWorld.java | 103 ++-- .../early/common/MixinWorldProvider.java | 24 +- .../mixin/early/common/MixinWorldServer.java | 20 + .../early/common/MixinWorld_HeightLimit.java | 5 + .../mixin/early/common/MixinWorld_Tick.java | 4 + .../worldgen/MixinChunkProviderGenerate.java | 56 ++- ...MixinDedicatedServer_DefaultLevelType.java | 2 +- .../network/PacketEncoderColumn.java | 8 +- .../network/PacketEncoderCubes.java | 1 - .../network/PacketEncoderUnloadCube.java | 2 +- .../registry/CubicChunksRegistryManager.java | 18 - .../registry/ICubicChunksRegistryEntry.java | 21 - .../cubicchunks/server/ColumnWatcher.java | 13 - .../server/CubeProviderServer.java | 161 +------ .../cubicchunks/server/CubeWatcher.java | 19 +- .../server/CubicPlayerManager.java | 19 +- .../cubicchunks/server/SpawnCubes.java | 2 +- .../cubicchunks/server/chunkio/CubeIO.java | 302 ++++++++++++ .../server/chunkio/CubeInitLevel.java | 32 ++ .../server/chunkio/CubeLoaderCallback.java | 2 +- .../server/chunkio/CubeLoaderServer.java | 454 ++++++++---------- .../cubicchunks/server/chunkio/ICubeIO.java | 26 + .../server/chunkio/ICubeLoader.java | 29 +- .../server/chunkio/IONbtReader.java | 19 +- .../server/chunkio/IONbtWriter.java | 10 +- .../chunkio/IPreloadFailureDelegate.java | 14 + .../server/chunkio/LoadFailureException.java | 21 + .../server/chunkio/RegionCubeStorage.java | 26 +- .../chunkio/region/ShadowPagingRegion.java | 3 +- .../cubicchunks/util/Array3D.java | 48 ++ .../cubicchunks/util/BlockPosMap.java | 201 ++++++++ .../cubicchunks/util/BooleanArray3D.java | 78 +++ .../cubicchunks/util/ChunkMap.java | 209 ++++++++ .../cardinalstar/cubicchunks/util/Coords.java | 42 +- .../cubicchunks/util/DependencyGraph.java | 83 +++- .../cubicchunks/util/DoubleInterval.java | 15 + .../cubicchunks/util/ObjectPooler.java | 4 + .../cardinalstar/cubicchunks/util/XSTR.java | 6 +- .../world/CubicChunksSavedData.java | 70 +++ .../world/ICubicWorldProvider.java | 4 +- .../world/WorldSavedCubicChunksData.java | 79 --- .../cubicchunks/world/cube/Cube.java | 75 ++- .../cube/blockview/IMutableBlockView.java | 2 +- .../world/cube/blockview/SafeBlockView.java | 47 ++ .../cube/blockview/SafeMutableBlockView.java | 81 ++++ .../world/cube/blockview/SubBlockView.java | 18 +- ...lockView.java => SubMutableBlockView.java} | 4 +- .../cube/blockview/UniformBlockView.java | 26 + .../world/savedata/WorldFormatSavedData.java | 62 +++ .../world/worldgen/FeatureCubicGenerator.java | 4 +- .../world/worldgen/MapGenCaveFluids.java | 25 +- .../world/worldgen/MapGenCavesCubic.java | 4 +- .../worldgen/SeedBasedCubicGenerator.java | 10 +- .../worldgen/SurfacePatcherPopulator.java | 16 - .../world/worldgen/WorldGenerators.java | 49 +- .../compat/DeepslateCubePopulator.java | 25 +- .../worldgen/{ => data}/FastCubePosMap.java | 2 +- .../worldgen/{ => data}/MutableCubePos.java | 9 +- .../worldgen/data/NoisePrecalculator.java | 176 +++++++ .../world/worldgen/data/SamplerFactory.java | 10 + .../worldgen/modern/NoodleCaveGenerator.java | 81 ++++ .../modern/SpaghettiCaveGenerator.java | 73 +++ .../worldgen/noise/BlockNoiseSampler.java | 8 + .../world/worldgen/noise/NoiseSampler.java | 8 + .../world/worldgen/noise/OctavesSampler.java | 54 +++ .../world/worldgen/noise/ScaledNoise.java | 30 ++ .../worldgen/noise/SimplexNoiseSampler.java | 183 +++++++ .../vanilla/PrecalcedVanillaOctaves.java | 214 +++++++++ .../worldgen/vanilla/PrecalculableNoise.java | 8 + ...erator.java => VanillaWorldGenerator.java} | 416 ++++++++-------- .../assets/cubicchunks/lang/en_US.lang | 5 + 110 files changed, 3794 insertions(+), 1803 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/registry/AbstractRegistryEntry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java rename src/main/java/com/cardinalstar/cubicchunks/{registry/CubicChunksRegistry.java => api/registry/Registry.java} (52%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/world/Precalculable.java rename src/main/java/com/cardinalstar/cubicchunks/api/world/storage/{StorageFormatProviderBase.java => StorageFormatFactory.java} (52%) rename src/main/java/com/cardinalstar/cubicchunks/api/worldgen/{LoadingData.java => BuiltinWorldDecorators.java} (52%) delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java rename src/main/java/com/cardinalstar/cubicchunks/api/worldgen/{ICubeGenerator.java => IWorldGenerator.java} (50%) delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/VanillaCompatibilityGeneratorProviderBase.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java rename src/main/java/com/cardinalstar/cubicchunks/{event/events/CubeDataEvent.java => api/worldgen/decoration/ICubePopulator.java} (55%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubeTerrainGenerator.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubicPopulator.java rename src/main/java/com/cardinalstar/cubicchunks/{world/type => api/worldtype}/VanillaCubicWorldType.java (77%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/event/events/ColumnEvent.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinEntity_Brightness.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistryManager.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/registry/ICubicChunksRegistryEntry.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeInitLevel.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/LoadFailureException.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/Array3D.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/BlockPosMap.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/ChunkMap.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/DoubleInterval.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/CubicChunksSavedData.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/WorldSavedCubicChunksData.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeBlockView.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeMutableBlockView.java rename src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/{MutableSubBlockView.java => SubMutableBlockView.java} (89%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/UniformBlockView.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SurfacePatcherPopulator.java rename src/main/java/com/cardinalstar/cubicchunks/world/worldgen/{ => data}/FastCubePosMap.java (97%) rename src/main/java/com/cardinalstar/cubicchunks/world/worldgen/{ => data}/MutableCubePos.java (86%) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/SamplerFactory.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/OctavesSampler.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/ScaledNoise.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalculableNoise.java rename src/main/java/com/cardinalstar/cubicchunks/worldgen/{VanillaCompatibilityGenerator.java => VanillaWorldGenerator.java} (54%) diff --git a/dependencies.gradle b/dependencies.gradle index 97f97d30..2b61c462 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,12 +34,18 @@ * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - api("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") - api("io.github.opencubicchunks:regionlib:0.78.0-SNAPSHOT") + implementation("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") + implementation("io.github.opencubicchunks:regionlib:0.78.0-SNAPSHOT") runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.4-GTNH:dev") runtimeOnlyNonPublishable(rfg.deobf("curse.maven:spark-361579:4271867")) + compileOnly("org.jetbrains:annotations:26.0.2") + devOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.2.21-GTNH-daily:dev") // devOnlyNonPublishable("com.github.GTNewHorizons:Angelica:1.0.0-beta56:dev") + + devOnlyNonPublishable rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612") + + devOnlyNonPublishable("com.github.GTNewHorizons:Realistic-World-Gen:alpha-1.5.0") } diff --git a/repositories.gradle b/repositories.gradle index fdb63109..078ccbd8 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -3,6 +3,6 @@ repositories { mavenCentral() maven { - setUrl("https://oss.sonatype.org/content/repositories/public/") + setUrl("https://oss.sonatype.org/service/local/repositories/snapshots/content/") } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index 41d8d904..d284d0d9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -31,7 +31,8 @@ import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.world.World; -import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.WorldServer; +import net.minecraft.world.storage.ISaveHandler; import net.minecraftforge.common.MinecraftForge; import org.apache.logging.log4j.Level; @@ -39,8 +40,8 @@ import org.apache.logging.log4j.Logger; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; -import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatProviderBase; -import com.cardinalstar.cubicchunks.api.worldgen.VanillaCompatibilityGeneratorProviderBase; +import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatFactory; +import com.cardinalstar.cubicchunks.api.worldtype.VanillaCubicWorldType; import com.cardinalstar.cubicchunks.event.handlers.ClientEventHandler; import com.cardinalstar.cubicchunks.event.handlers.CommonEventHandler; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldSettings; @@ -49,9 +50,7 @@ import com.cardinalstar.cubicchunks.server.chunkio.RegionCubeStorage; import com.cardinalstar.cubicchunks.util.CompatHandler; import com.cardinalstar.cubicchunks.util.SideUtils; -import com.cardinalstar.cubicchunks.world.type.VanillaCubicWorldType; import com.cardinalstar.cubicchunks.world.worldgen.WorldGenerators; -import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; import com.cardinalstar.cubicchunks.worldgen.WorldgenHangWatchdog; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; @@ -64,12 +63,6 @@ import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; -import cpw.mods.fml.common.event.FMLInitializationEvent; -import cpw.mods.fml.common.event.FMLPostInitializationEvent; -import cpw.mods.fml.common.event.FMLPreInitializationEvent; -import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; -import cpw.mods.fml.common.event.FMLServerStartingEvent; -import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.network.NetworkCheckHandler; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.internal.NetworkModHolder; @@ -125,8 +118,11 @@ public class CubicChunks { public void preInit(FMLPreInitializationEvent e) { LOGGER = e.getModLog(); - registerVanillaCompatibilityGeneratorProvider(); registerAnvil3dStorageFormatProvider(); + VanillaCubicWorldType.init(); + + LOGGER.debug("Registered world types"); + try { ConfigurationManager.registerConfig(CubicChunksConfig.class); } catch (ConfigException ex) { @@ -149,8 +145,6 @@ public String call() throws Exception { return message; } }); - VanillaCubicWorldType.create(); - LOGGER.debug("Registered world types"); // we have to redo the check for network compatibility because it depends on config // and config is done after forge does the check @@ -201,31 +195,10 @@ public void onServerAboutToStart(FMLServerAboutToStartEvent event) { }); } - @SubscribeEvent - public static void registerVanillaCompatibilityGeneratorProvider() { - VanillaCompatibilityGeneratorProviderBase.REGISTRY.register( - VanillaCompatibilityGeneratorProviderBase.DEFAULT, - new VanillaCompatibilityGeneratorProviderBase() { - - @Override - public VanillaCompatibilityGenerator provideGenerator(IChunkProvider vanillaChunkGenerator, - World world) { - return new VanillaCompatibilityGenerator(vanillaChunkGenerator, world); - } - }.setRegistryName(VanillaCompatibilityGeneratorProviderBase.DEFAULT) - .setUnlocalizedName("cubicchunks.gui.worldmenu.cc_default")); - } - - @SubscribeEvent public static void registerAnvil3dStorageFormatProvider() { - StorageFormatProviderBase.REGISTRY.register(StorageFormatProviderBase.DEFAULT, new StorageFormatProviderBase() { - - @Override - public ICubicStorage provideStorage(World world, Path path) throws IOException { - return new RegionCubeStorage(path); - } - }.setRegistryName(StorageFormatProviderBase.DEFAULT) - .setUnlocalizedName("cubicchunks.gui.storagefmt.anvil3d")); + StorageFormatFactory.REGISTRY.register( + StorageFormatFactory.DEFAULT, + new DefaultStorageFormatFactory()); } @NetworkCheckHandler @@ -304,4 +277,30 @@ public static void bigWarning(String format, Object... data) { } LOGGER.log(Level.WARN, "****************************************"); } + + private static class DefaultStorageFormatFactory extends StorageFormatFactory { + + public DefaultStorageFormatFactory() { + setRegistryName(StorageFormatFactory.DEFAULT); + setUnlocalizedName("cubicchunks.gui.storagefmt.anvil3d"); + } + + @Override + public Path getWorldSaveDirectory(ISaveHandler saveHandler, WorldServer worldServer) { + Path path = worldServer.getSaveHandler() + .getWorldDirectory() + .toPath(); + + if (worldServer.provider.getSaveFolder() != null) { + return path.resolve(worldServer.provider.getSaveFolder()); + } else { + return path; + } + } + + @Override + public ICubicStorage provideStorage(World world, Path path) throws IOException { + return new RegionCubeStorage(path); + } + } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java index 3b99cc10..163150ad 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java @@ -21,6 +21,7 @@ package com.cardinalstar.cubicchunks; +import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -43,7 +44,6 @@ import net.minecraft.command.ICommandSender; import net.minecraft.command.WrongUsageException; import net.minecraft.util.ChatComponentTranslation; -import net.minecraft.util.ResourceLocation; import com.cardinalstar.cubicchunks.command.CubicCommandBase; import com.cardinalstar.cubicchunks.command.SubCommandBase; @@ -52,7 +52,6 @@ import com.google.common.collect.TreeRangeSet; import com.gtnewhorizon.gtnhlib.config.Config; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; - import cpw.mods.fml.client.event.ConfigChangedEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -71,12 +70,12 @@ public class CubicChunksConfig { public static boolean optimizedCompatibilityGenerator = true; @Config.LangKey("cubicchunks.config.force_cc") - @Config.Comment("Determines when a cubic chunks world should be created for non-cubic-chunks world types.\n" - + "NONE - only when cubic chunks world type\n" - + "NEW_WORLD - only for newly created worlds\n" - + "LOAD_NOT_EXCLUDED - load all worlds as cubic chunks, except excluded dimensions\n" - + "ALWAYS - load everything as cubic chunks. Overrides forceDimensionExcludes") - public static ForceCCMode forceLoadCubicChunks = ForceCCMode.NONE; + @Config.Comment(""" + Determines when a cubic chunks world should be created for non-cubic-chunks world types. + DEFAULT - only when cubic chunks world type + LOAD_NOT_EXCLUDED - load all worlds as cubic chunks, except excluded dimensions + ALWAYS - load everything as cubic chunks. Overrides forceDimensionExcludes""") + public static ForceCCMode forceLoadCubicChunks = ForceCCMode.DEFAULT; @Config.LangKey("cubicchunks.config.cubegen_per_tick") @Config.Comment("The maximum number of cubic chunks to generate per tick.") @@ -140,10 +139,6 @@ public class CubicChunksConfig { @Config.Comment("Above this height, biome temperature will no longer change") public static int biomeTemperatureScaleMaxY = 256; - @Config.LangKey("cubicchunks.config.compatibility_generator_type") - @Config.Comment("Vanilla compatibility generator type, which will convert vanilla world type generators output in cubic") - public static String compatibilityGeneratorType = "cubicchunks:default"; - @Config.LangKey("cubicchunks.config.storage_format") @Config.Comment("The storage format. Note: this will be used for all newly created worlds. Existing worlds will continue to use the format they were created with.\n" + "If empty, the storage format for new worlds will be determined automatically.") @@ -228,6 +223,18 @@ public static final class VanillaClients { @Config.Ignore public static Map modMaxCubesPerChunkloadingTicket = new HashMap<>(); + @Config.LangKey("cubicchunks.config.optimizations") + @Config.Comment("Options controlling various optimizations.") + public static Optimizations optimizations = new Optimizations(); + + public static final class Optimizations { + + @Config.LangKey("cubicchunks.config.optimizations.background_threads") + @Config.Comment("Maximum number of threads to use for background tasks (world I/O, noise generation, etc).") + public int backgroundThreads = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors() / 2; + + } + static { modMaxCubesPerChunkloadingTicket.put("cubicchunks", defaultMaxCubesPerChunkloadingTicket); } @@ -355,17 +362,6 @@ public static void setVerticalViewDistance(int value) { sync(); } - public static void disableCubicChunks() { - forceLoadCubicChunks = ForceCCMode.NONE; - sync(); - } - - public static void setGenerator(ResourceLocation generatorTypeIn) { - if (forceLoadCubicChunks == ForceCCMode.NONE) forceLoadCubicChunks = ForceCCMode.NEW_WORLD; - compatibilityGeneratorType = generatorTypeIn.toString(); - sync(); - } - public static boolean isDimensionExcluded(int dimension) { if (excludedDimensionsRanges == null) { initDimensionRanges(); @@ -374,9 +370,11 @@ public static boolean isDimensionExcluded(int dimension) { } public enum ForceCCMode { - NONE, - NEW_WORLD, + /// only when cubic chunks world type + DEFAULT, + /// load all worlds as cubic chunks, except excluded dimensions LOAD_NOT_EXCLUDED, + /// load everything as cubic chunks. Overrides forceDimensionExcludes ALWAYS } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java index be31abce..11efa04f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java @@ -38,8 +38,8 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; -import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderServer; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; +import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; @@ -216,7 +216,7 @@ public interface ICube extends XYZAddressable { /** * Check whether this cube was populated, i.e. if this cube was passed as argument to - * {@link ICubeGenerator#populate(ICube)}. Check there for more information regarding + * {@link IWorldGenerator#populate(ICube)}. Check there for more information regarding * population. * * @return {@code true} if this cube has been populated, {@code false} otherwise @@ -225,7 +225,7 @@ public interface ICube extends XYZAddressable { /** * Check whether this cube was fully populated, i.e. if any cube potentially writing to this cube was passed as an - * argument to {@link ICubeGenerator#populate(ICube)}. Check there for more + * argument to {@link IWorldGenerator#populate(ICube)}. Check there for more * information regarding population * * @return {@code true} if this cube has been populated, {@code false} otherwise @@ -246,17 +246,17 @@ public interface ICube extends XYZAddressable { */ boolean isInitialLightingDone(); - default CubeLoaderServer.CubeInitLevel getInitLevel() { - if (isPopulated() && isInitialLightingDone() && isSurfaceTracked()) { - return CubeLoaderServer.CubeInitLevel.Lit; - } else if (isPopulated()) { - return CubeLoaderServer.CubeInitLevel.Populated; + default CubeInitLevel getInitLevel() { + if (isFullyPopulated() && isInitialLightingDone() && isSurfaceTracked()) { + return CubeInitLevel.Lit; + } else if (isFullyPopulated()) { + return CubeInitLevel.Populated; } else { - return CubeLoaderServer.CubeInitLevel.Generated; + return CubeInitLevel.Generated; } } - default boolean isInitializedToLevel(CubeLoaderServer.CubeInitLevel initLevel) { + default boolean isInitializedToLevel(CubeInitLevel initLevel) { return getInitLevel().ordinal() >= initLevel.ordinal(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/registry/AbstractRegistryEntry.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/AbstractRegistryEntry.java new file mode 100644 index 00000000..3fc97475 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/AbstractRegistryEntry.java @@ -0,0 +1,38 @@ +package com.cardinalstar.cubicchunks.api.registry; + +import net.minecraft.util.StatCollector; + +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; + +@SuppressWarnings("unchecked") +public class AbstractRegistryEntry implements IRegistryEntry { + + public UniqueIdentifier registryName; + public String unlocalizedName; + + @Override + public TSelf setRegistryName(UniqueIdentifier name) { + registryName = name; + return (TSelf) this; + } + + @Override + public UniqueIdentifier getRegistryName() { + return registryName; + } + + public TSelf setUnlocalizedName(String name) { + unlocalizedName = name; + return (TSelf) this; + } + + @Override + public String getUnlocalizedName() { + return unlocalizedName; + } + + @Override + public String getLocalizedName() { + return StatCollector.translateToLocal(getUnlocalizedName()); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java new file mode 100644 index 00000000..f4ef97bc --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java @@ -0,0 +1,22 @@ +package com.cardinalstar.cubicchunks.api.registry; + +import javax.annotation.Nullable; + +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; + +/** + * This class is basically a simplified recreation of the forge registry system from 1.12. I liked how it works + * and keeps things organized, so I wanted to do it as well. + * + * @param The base class of the registries. + */ +public interface IRegistryEntry { + + V setRegistryName(UniqueIdentifier name); + + @Nullable + UniqueIdentifier getRegistryName(); + + String getLocalizedName(); + String getUnlocalizedName(); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/Registry.java similarity index 52% rename from src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistry.java rename to src/main/java/com/cardinalstar/cubicchunks/api/registry/Registry.java index bdedb413..267093e7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/Registry.java @@ -1,16 +1,17 @@ -package com.cardinalstar.cubicchunks.registry; +package com.cardinalstar.cubicchunks.api.registry; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import net.minecraft.util.ResourceLocation; +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; -public class CubicChunksRegistry { +public class Registry { - private final Map entries = new HashMap<>(); + private final Map entries = new HashMap<>(); - public CubicChunksRegistry register(ResourceLocation name, V entry) { + @SuppressWarnings("UnusedReturnValue") + public Registry register(UniqueIdentifier name, V entry) { if (entries.containsKey(name)) { throw new IllegalStateException("Duplicate entry: " + name); } @@ -18,7 +19,7 @@ public CubicChunksRegistry register(ResourceLocation name, V entry) { return this; } - public V get(ResourceLocation name) { + public V get(UniqueIdentifier name) { return entries.get(name); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java index 58bbf051..acad1ced 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java @@ -33,6 +33,8 @@ @ParametersAreNonnullByDefault public class Box implements Iterable { + public static final Box CUBE = new Box(0, 0, 0, 15, 15, 15); + protected int x1, y1, z1; protected int x2, y2, z2; @@ -69,6 +71,18 @@ public int getZ2() { return z2; } + public int getSizeX() { + return x2 - x1; + } + + public int getSizeY() { + return y2 - y1; + } + + public int getSizeZ() { + return z2 - z1; + } + public boolean contains(Box other) { return x1 <= other.x1 && x2 >= other.x2 && y1 <= other.y1 && y2 >= other.y2 && z1 <= other.z1 && z2 >= other.z2; } @@ -241,6 +255,6 @@ public Box.Mutable add(int dx, int dy, int dz) { } public static Box horizontalChunkSlice(int startY, int heightY) { - return new Box(0, startY, 0, 16, startY + heightY, 16); + return new Box(0, startY, 0, 15, startY + heightY, 15); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/world/ICubicWorldType.java b/src/main/java/com/cardinalstar/cubicchunks/api/world/ICubicWorldType.java index d5b34512..31aefde3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/world/ICubicWorldType.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/world/ICubicWorldType.java @@ -20,21 +20,21 @@ */ package com.cardinalstar.cubicchunks.api.world; -import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.world.World; import net.minecraft.world.WorldServer; +import org.jetbrains.annotations.NotNull; + import com.cardinalstar.cubicchunks.api.IntRange; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; @ParametersAreNonnullByDefault public interface ICubicWorldType { - // TODO: Make it Nonnull. VanillaCubic uses null - @Nullable - ICubeGenerator createCubeGenerator(World world); + @NotNull + IWorldGenerator createCubeGenerator(World world); IntRange calculateGenerationHeightRange(WorldServer world); diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/world/Precalculable.java b/src/main/java/com/cardinalstar/cubicchunks/api/world/Precalculable.java new file mode 100644 index 00000000..ed03a98c --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/world/Precalculable.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.api.world; + +/// Something that can queue up precalculation jobs +public interface Precalculable { + + void precalculate(int cubeX, int cubeY, int cubeZ); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatProviderBase.java b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java similarity index 52% rename from src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatProviderBase.java rename to src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java index 3f9928fb..dc442e9f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatProviderBase.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java @@ -23,52 +23,20 @@ import java.io.IOException; import java.nio.file.Path; -import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.storage.ISaveHandler; -import com.cardinalstar.cubicchunks.registry.CubicChunksRegistry; -import com.cardinalstar.cubicchunks.registry.ICubicChunksRegistryEntry; +import com.cardinalstar.cubicchunks.api.registry.AbstractRegistryEntry; +import com.cardinalstar.cubicchunks.api.registry.Registry; +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; -public abstract class StorageFormatProviderBase implements ICubicChunksRegistryEntry { +public abstract class StorageFormatFactory extends AbstractRegistryEntry { - public static final ResourceLocation DEFAULT = new ResourceLocation("cubicchunks", "anvil3d"); - public static CubicChunksRegistry REGISTRY = new CubicChunksRegistry<>();; + public static final UniqueIdentifier DEFAULT = new UniqueIdentifier("cubicchunks:anvil3d"); + public static final Registry REGISTRY = new Registry<>(); - public ResourceLocation registryName; - public String unlocalizedName; - - @Override - public ResourceLocation getRegistryName() { - return this.registryName; - } - - @Override - public StorageFormatProviderBase setRegistryName(ResourceLocation registryNameIn) { - this.registryName = registryNameIn; - return this; - } - - @Override - public Class getRegistryType() { - return StorageFormatProviderBase.class; - } - - public String getUnlocalizedName() { - return this.unlocalizedName; - } - - public StorageFormatProviderBase setUnlocalizedName(String nameIn) { - this.unlocalizedName = nameIn; - return this; - } + public abstract Path getWorldSaveDirectory(ISaveHandler saveHandler, WorldServer worldServer); public abstract ICubicStorage provideStorage(World world, Path path) throws IOException; - - /** - * @return whether or not this storage format may be used as the default - */ - public boolean canBeDefault() { - return false; - } - } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/LoadingData.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/BuiltinWorldDecorators.java similarity index 52% rename from src/main/java/com/cardinalstar/cubicchunks/api/worldgen/LoadingData.java rename to src/main/java/com/cardinalstar/cubicchunks/api/worldgen/BuiltinWorldDecorators.java index 44702827..777815a1 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/LoadingData.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/BuiltinWorldDecorators.java @@ -20,60 +20,10 @@ */ package com.cardinalstar.cubicchunks.api.worldgen; -import java.util.Objects; +import com.cardinalstar.cubicchunks.api.worldgen.impl.StandardWorldDecorator; -import javax.annotation.Nullable; +public class BuiltinWorldDecorators { -import net.minecraft.nbt.NBTTagCompound; - -public class LoadingData { - - private final POS pos; - @Nullable - private NBTTagCompound nbt; - - public LoadingData(POS pos, @Nullable NBTTagCompound nbt) { - this.pos = pos; - this.nbt = nbt; - } - - public POS getPos() { - return pos; - } - - /** - * Returns chunk loading NBT data. Null if chunk not found. - * - * @return The chunk loading NBT data, or null - */ - @Nullable - public NBTTagCompound getNbt() { - return nbt; - } - - public void setNbt(NBTTagCompound tag) { - this.nbt = tag; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - LoadingData that = (LoadingData) o; - return pos.equals(that.pos); - } - - @Override - public int hashCode() { - return Objects.hash(pos); - } - - @Override - public String toString() { - return "LoadingData(" + pos + ')'; - } + public static final StandardWorldDecorator VANILLA = new StandardWorldDecorator(); + public static final StandardWorldDecorator CUBIC_VANILLA = new StandardWorldDecorator(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java deleted file mode 100644 index f7d5ab1b..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.api.worldgen; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.BiConsumer; - -import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.World; - -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubeTerrainGenerator; -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; -import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.DependencyGraph; -import com.cardinalstar.cubicchunks.world.cube.Cube; -import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; -import com.google.common.base.Preconditions; - -public class CubeGeneratorsRegistry { - - /** List of populators added by other mods to vanilla compatibility generator type */ - private static final List customPopulatorsForFlatCubicGenerator = new ArrayList(); - private static final List>> cubeLoadingCallbacks = new ArrayList<>( - 2); - private static final List>> columnLoadingCallbacks = new ArrayList<>( - 2); - - private static final Collection>> cubeLoadingCallbacksView = Collections - .unmodifiableCollection(cubeLoadingCallbacks); - private static final Collection>> columnLoadingCallbacksView = Collections - .unmodifiableCollection(columnLoadingCallbacks); - - private static final DependencyGraph> vanillaGenerators = new DependencyGraph<>(); - - private static final DependencyGraph vanillaPopulators = new DependencyGraph<>(); - - /** - * Register a world generator that runs exclusively in the vanilla compatibility generator. This will not run for - * other world types. - */ - public static void registerVanillaGenerator(String name, ICubeTerrainGenerator generator, String... dependencies) { - Preconditions.checkNotNull(name); - Preconditions.checkNotNull(generator); - - vanillaGenerators.addObject(name, generator); - - for (String dep : dependencies) { - vanillaGenerators.addDependency(name, dep); - } - } - - /** - * Adds a dependency between vanilla terrain generators. The dependency will be ran before the given generator. - */ - public static void addVanillaGeneratorDependency(String generator, String dependency) { - Preconditions.checkNotNull(generator); - Preconditions.checkNotNull(dependency); - - vanillaGenerators.addDependency(generator, dependency); - } - - public static void generateVanillaCube(VanillaCompatibilityGenerator generator, World world, Cube cube) { - List> generators = vanillaGenerators.sorted(); - - for (int i = 0, generatorsSize = generators.size(); i < generatorsSize; i++) { - ICubeTerrainGenerator cubeGen = generators.get(i); - - cubeGen.generate(generator, world, cube); - } - } - - /** - * Register a world populator that runs exclusively in the vanilla compatibility populator. This will not run for - * other world types. - */ - public static void registerVanillaPopulator(String name, ICubicPopulator populator, String... dependencies) { - Preconditions.checkNotNull(name); - Preconditions.checkNotNull(populator); - - vanillaPopulators.addObject(name, populator); - - for (String dep : dependencies) { - vanillaPopulators.addDependency(name, dep); - } - } - - /** - * Adds a dependency between vanilla terrain populators. The dependency will be ran before the given populator. - */ - public static void addVanillaPopulatorDependency(String populator, String dependency) { - Preconditions.checkNotNull(populator); - Preconditions.checkNotNull(dependency); - - vanillaPopulators.addDependency(populator, dependency); - } - - public static void populateVanillaCube(World world, CubePos pos) { - List populators = vanillaPopulators.sorted(); - - for (int i = 0, populatorsSize = populators.size(); i < populatorsSize; i++) { - ICubicPopulator cubeGen = populators.get(i); - - cubeGen.generate(world, pos); - } - } - - /** - * Registers a callback invoked after loading cube NBT from disk. This callback will get called even if no data is - * found, potentially allowing - * to prepare data for world generation asynchronously in a cache. - * - * @param cubeCallback the callback to be registered - */ - public static void registerCubeAsyncLoadingCallback(BiConsumer> cubeCallback) { - cubeLoadingCallbacks.add(cubeCallback); - } - - /** - * Registers a callback invoked after loading column NBT from disk. This callback will get called even if no data is - * found, potentially allowing - * to prepare data for world generation asynchronously in a cache. - * - * @param columnCallback the callback to be registered - */ - public static void registerColumnAsyncLoadingCallback(BiConsumer> columnCallback) { - columnLoadingCallbacks.add(columnCallback); - } - - public static Collection>> getCubeAsyncLoadingCallbacks() { - return cubeLoadingCallbacksView; - } - - public static Collection>> getColumnAsyncLoadingCallbacks() { - return columnLoadingCallbacksView; - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java new file mode 100644 index 00000000..92c464dd --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java @@ -0,0 +1,13 @@ +package com.cardinalstar.cubicchunks.api.worldgen; + +import java.util.List; + +public interface DependencyRegistry { + + void register(String name, T value, String... deps); + void registerTarget(String name, String... deps); + void addDependency(String from, String to); + void removeDependency(String from, String to); + + List sorted(); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java new file mode 100644 index 00000000..83a25ef4 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java @@ -0,0 +1,27 @@ +package com.cardinalstar.cubicchunks.api.worldgen; + +import java.util.List; + +import net.minecraft.world.chunk.Chunk; + +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.google.common.collect.ImmutableList; + +public class GenerationResult { + + public final T object; + public final ImmutableList columnSideEffects; + public final ImmutableList cubeSideEffects; + + public GenerationResult(T object) { + this.object = object; + this.columnSideEffects = ImmutableList.of(); + this.cubeSideEffects = ImmutableList.of(); + } + + public GenerationResult(T object, List columnSideEffects, List cubeSideEffects) { + this.object = object; + this.columnSideEffects = columnSideEffects == null ? ImmutableList.of() : ImmutableList.copyOf(columnSideEffects); + this.cubeSideEffects = cubeSideEffects == null ? ImmutableList.of() : ImmutableList.copyOf(cubeSideEffects); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/ICubeGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java similarity index 50% rename from src/main/java/com/cardinalstar/cubicchunks/api/worldgen/ICubeGenerator.java rename to src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java index 8faf9624..8bd8e610 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/ICubeGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java @@ -21,12 +21,10 @@ package com.cardinalstar.cubicchunks.api.worldgen; import java.util.List; -import java.util.Optional; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; -import net.minecraft.block.Block; import net.minecraft.entity.EnumCreatureType; import net.minecraft.world.ChunkPosition; import net.minecraft.world.World; @@ -34,29 +32,10 @@ import net.minecraft.world.chunk.Chunk; import com.cardinalstar.cubicchunks.api.ICube; -import com.cardinalstar.cubicchunks.api.util.Box; import com.cardinalstar.cubicchunks.world.cube.Cube; @ParametersAreNonnullByDefault -public interface ICubeGenerator { - - Box RECOMMENDED_FULL_POPULATOR_REQUIREMENT = new Box( - -1, - -1, - -1, // ignore jungle trees and other very tall structures and let them reach potentially unloaded cubes - 0, - 0, - 0); - - Box RECOMMENDED_GENERATE_POPULATOR_REQUIREMENT = new Box( - 1, - 1, - 1, // ignore jungle trees and other very tall structures and let them reach potentially unloaded cubes - 0, - 0, - 0); - - Box NO_REQUIREMENT = new Box(0, 0, 0, 0, 0, 0); +public interface IWorldGenerator { /** * Generate a new cube @@ -67,14 +46,12 @@ public interface ICubeGenerator { * * @return A CubePrimer with the generated blocks */ - Cube provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ); + GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ); /** * Generate column-global information such as biome data - * - * @param column the target column */ - void generateColumn(Chunk column); + GenerationResult provideColumn(World world, int columnX, int columnZ); /** * Populate a cube with multi-block structures that can cross cube boundaries such as trees and ore veins. @@ -91,80 +68,23 @@ public interface ICubeGenerator { */ void populate(Cube cube); - default Optional tryGenerateCube(Chunk chunk, int cubeX, int cubeY, int cubeZ, boolean forceGenerate) { - return Optional.of(this.provideCube(chunk, cubeX, cubeY, cubeZ)); - } - - default Optional tryGenerateColumn(World world, int columnX, int columnZ, @Nullable Block[] blocks, - @Nullable byte[] blockMeta, boolean forceGenerate) { - Chunk column = new Chunk(world, columnX, columnZ); - this.generateColumn(column); - return Optional.of(column); - } - - default boolean supportsConcurrentCubeGeneration() { - return false; - } - - default boolean supportsConcurrentColumnGeneration() { - return false; - } - - /** - * Checks whether the generator is ready to generate a given cube. - * - * @param cubeX X coordinate of the cube - * @param cubeY Y coordinate of the cube - * @param cubeZ Z coordinate of the cube - * @return The generator state. READY means that the asynchronous part of generation is done. - * WAITING means that async part is in progress. FAIL if cube cannot be generated - */ - default GeneratorReadyState pollAsyncCubeGenerator(int cubeX, int cubeY, int cubeZ) { - return GeneratorReadyState.READY; - } - - /** - * Checks whether the generator is ready to generate a given column. - * - * @param chunkX X coordinate of the column - * @param chunkZ Z coordinate of the column - * @return The generator state. READY means that the asynchronous part of generation is done. - * WAITING means that async part is in progress. FAIL if cube cannot be generated - */ - default GeneratorReadyState pollAsyncColumnGenerator(int chunkX, int chunkZ) { - return GeneratorReadyState.READY; - } - - /** - * Checks whether the generator is ready to generate a given column. - * - * @param cubeX X coordinate of the cube - * @param cubeY Y coordinate of the cube - * @param cubeZ Z coordinate of the cube - * @return The generator state. READY means that the asynchronous part of generation is done. - * WAITING means that async part is in progress. FAIL if cube cannot be generated - */ - default GeneratorReadyState pollAsyncCubePopulator(int cubeX, int cubeY, int cubeZ) { - return GeneratorReadyState.READY; - } - /** * Called to reload structures that apply to {@code cube}. Mostly used to prepare calls to - * {@link ICubeGenerator#getPossibleCreatures(EnumCreatureType, int, int, int)}
+ * {@link IWorldGenerator#getPossibleCreatures(EnumCreatureType, int, int, int)}
* * @param cube The cube being loaded * - * @see ICubeGenerator#recreateStructures(Chunk) for the 2D-equivalent of this method + * @see IWorldGenerator#recreateStructures(Chunk) for the 2D-equivalent of this method */ void recreateStructures(ICube cube); /** * Called to reload structures that apply to {@code column}. Mostly used to prepare calls to - * {@link ICubeGenerator#getPossibleCreatures(EnumCreatureType, int, int, int)}
+ * {@link IWorldGenerator#getPossibleCreatures(EnumCreatureType, int, int, int)}
* * @param column The column being loaded * - * @see ICubeGenerator#recreateStructures(ICube) for the 3D-equivalent of this method + * @see IWorldGenerator#recreateStructures(ICube) for the 3D-equivalent of this method */ void recreateStructures(Chunk column); @@ -186,27 +106,9 @@ default GeneratorReadyState pollAsyncCubePopulator(int cubeX, int cubeY, int cub * a stronghold. * * @param name the name of the structure - * @param pos find the structure closest to this position - * @param findUnexplored true if should also find not yet generated structures * * @return the position of the structure, or {@code null} if none could be found */ @Nullable ChunkPosition getNearestStructure(String name, int x, int y, int z); - - enum GeneratorReadyState { - /** - * Indicates that the generator is ready to generate a given cube or column - */ - READY, - /** - * Indicates that the generator is waiting for some resources to generate a given cube or column. - * Generating may be possible in this state, but it could fail and take longer amount of time. - */ - WAITING, - /** - * Generating a cube or column will most likely fail in this state. - */ - FAIL - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/VanillaCompatibilityGeneratorProviderBase.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/VanillaCompatibilityGeneratorProviderBase.java deleted file mode 100644 index 734ed4d1..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/VanillaCompatibilityGeneratorProviderBase.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.api.worldgen; - -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.World; -import net.minecraft.world.chunk.IChunkProvider; - -import com.cardinalstar.cubicchunks.registry.CubicChunksRegistry; -import com.cardinalstar.cubicchunks.registry.ICubicChunksRegistryEntry; - -public abstract class VanillaCompatibilityGeneratorProviderBase - implements ICubicChunksRegistryEntry { - - public static final ResourceLocation DEFAULT = new ResourceLocation("cubicchunks", "default"); - public static CubicChunksRegistry REGISTRY = new CubicChunksRegistry<>(); - - public ResourceLocation registryName; - public String unlocalizedName; - - @Override - public VanillaCompatibilityGeneratorProviderBase setRegistryName(ResourceLocation registryNameIn) { - registryName = registryNameIn; - return this; - } - - @Override - public ResourceLocation getRegistryName() { - return registryName; - } - - @Override - public Class getRegistryType() { - return VanillaCompatibilityGeneratorProviderBase.class; - } - - public VanillaCompatibilityGeneratorProviderBase setUnlocalizedName(String nameIn) { - unlocalizedName = nameIn; - return this; - } - - public String getUnlocalizedName() { - return unlocalizedName; - } - - public abstract ICubeGenerator provideGenerator(IChunkProvider vanillaChunkGenerator, World world); -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java new file mode 100644 index 00000000..b9661852 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java @@ -0,0 +1,14 @@ +package com.cardinalstar.cubicchunks.api.worldgen; + +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; + +public interface WorldgenRegistry { + + /// Terrain generators create the general shape of the terrain and cannot interact with other cubes. This includes + /// any noise-based generation that doesn't require information from other cubes such as modern caves. + DependencyRegistry terrain(); + + /// Populators finalize cubes and put the 'window dressing' on the world - ores, trees, etc. + DependencyRegistry population(); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java new file mode 100644 index 00000000..af793230 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java @@ -0,0 +1,21 @@ +package com.cardinalstar.cubicchunks.api.worldgen.decoration; + +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; + +/// Generates the terrain for a cube. The cube is not in the world during generation and block operations must not be performed. +public interface ICubeGenerator { + + /// Hints to any off-thread precachers to submit a generation request for a cube that will be generated in the + /// near-future. + /// Result may or may not be used, this is just an optimization to do as much computation in a background thread as + /// possible. + default void pregenerate(World world, CubePos pos) { + + } + + /// Fills a cube with various terrain features. + void generate(World world, Cube cube); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeDataEvent.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java similarity index 55% rename from src/main/java/com/cardinalstar/cubicchunks/event/events/CubeDataEvent.java rename to src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java index 0f652c92..c8bb3fbb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeDataEvent.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java @@ -18,45 +18,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cardinalstar.cubicchunks.event.events; +package com.cardinalstar.cubicchunks.api.worldgen.decoration; -import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.ICube; +import com.cardinalstar.cubicchunks.api.worldgen.BuiltinWorldDecorators; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; -/** - * CubicChunks equivalent of {@link net.minecraftforge.event.world.ChunkDataEvent} - */ -public class CubeDataEvent extends CubeEvent { - - private final NBTTagCompound data; - - public CubeDataEvent(ICube cube, NBTTagCompound data) { - super(cube); - this.data = data; - } +/// Something that populates cubes. Has complete access to the world, just be careful to not cause cascading worldgen. +public interface ICubePopulator { - public NBTTagCompound getData() { - return data; - } - - /** - * CubicChunks equivalent of {@link net.minecraftforge.event.world.ChunkDataEvent.Load} - */ - public static class Load extends CubeDataEvent { + /// Hints to any off-thread precachers to submit a population request for a cube that will be populated in the + /// near-future. + /// Result may or may not be used, this is just an optimization to do as much computation in a background thread as + /// possible. + default void prepopulate(World world, CubePos pos) { - public Load(ICube cube, NBTTagCompound data) { - super(cube, data); - } } - /** - * CubicChunks equivalent of {@link net.minecraftforge.event.world.ChunkDataEvent.Save} - */ - public static class Save extends CubeDataEvent { - - public Save(ICube cube, NBTTagCompound data) { - super(cube, data); - } - } + /// Populates the cube + void populate(World world, Cube cube); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java new file mode 100644 index 00000000..1a640a41 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java @@ -0,0 +1,29 @@ +package com.cardinalstar.cubicchunks.api.worldgen.decoration; + +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.api.worldgen.impl.StandardWorldDecorator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; + +/// A decorator is responsible for doing any special terrain generation or population. Typically you'll want to use a +/// [StandardWorldDecorator] instead of implementing this yourself, because this is just a wrapper type. +public interface IWorldDecorator { + + /// Runs prior to [#generateCube(World, Cube)], when a cube is in the eager-loading queue and needs to be generated. + /// There is no guarantee pregenerated cubes will be eventually loaded. Cubes can be removed from the eager loading queue. + void pregenerate(World world, CubePos pos); + + /// Runs terrain generation on the cube. The cube is not in the world at this point and block operations should not + /// be performed on the world. + void generate(World world, Cube cube); + + /// Runs prior to [#populateCube(World, CubePos)], when a cube is in the eager-loading queue but needs to be + /// populated. There is no guarantee prepopulated cubes will be eventually populated. Cubes can be removed + /// from the eager loading queue. + void prepopulate(World world, CubePos pos); + + /// Populates the cube. The cube is in the world at this point, but be careful of causing cascading worldgen when + /// accessing blocks outside the current cube. Note that by convention MC populates the 16x16x16 box around the +X,+Y,+Z corner, not whole cube. + void populate(World world, Cube cube); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java new file mode 100644 index 00000000..fd8e7909 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java @@ -0,0 +1,56 @@ +package com.cardinalstar.cubicchunks.api.worldgen.impl; + +import java.util.List; + +import com.cardinalstar.cubicchunks.api.worldgen.DependencyRegistry; +import com.cardinalstar.cubicchunks.util.DependencyGraph; +import com.google.common.base.Preconditions; + +public class StandardDependencyRegistry implements DependencyRegistry { + + private final DependencyGraph graph = new DependencyGraph<>(); + + @Override + public void register(String name, T value, String... deps) { + Preconditions.checkNotNull(name); + Preconditions.checkNotNull(value); + + graph.addObject(name, value); + + for (String dep : deps) { + graph.addUnparsedDependency(name, dep); + } + } + + @Override + public void registerTarget(String name, String... deps) { + Preconditions.checkNotNull(name); + + graph.addTarget(name); + + for (String dep : deps) { + graph.addUnparsedDependency(name, dep); + } + } + + @Override + public void addDependency(String from, String to) { + Preconditions.checkNotNull(from); + Preconditions.checkNotNull(to); + + graph.addDependency(from, to); + } + + @Override + public void removeDependency(String from, String to) { + Preconditions.checkNotNull(from); + Preconditions.checkNotNull(to); + + graph.removeDependency(from, to); + } + + @Override + public List sorted() { + return graph.sorted(); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java new file mode 100644 index 00000000..91ba0645 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java @@ -0,0 +1,56 @@ +package com.cardinalstar.cubicchunks.api.worldgen.impl; + +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.api.worldgen.DependencyRegistry; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.IWorldDecorator; +import com.cardinalstar.cubicchunks.api.worldgen.WorldgenRegistry; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; + +public class StandardWorldDecorator implements WorldgenRegistry, IWorldDecorator { + + private final StandardDependencyRegistry terrain = new StandardDependencyRegistry<>(); + + private final StandardDependencyRegistry population = new StandardDependencyRegistry<>(); + + @Override + public DependencyRegistry terrain() { + return terrain; + } + + @Override + public DependencyRegistry population() { + return population; + } + + @Override + public void pregenerate(World world, CubePos pos) { + for (ICubeGenerator generator : terrain.sorted()) { + generator.pregenerate(world, pos); + } + } + + @Override + public void generate(World world, Cube cube) { + for (ICubeGenerator generator : terrain.sorted()) { + generator.generate(world, cube); + } + } + + @Override + public void prepopulate(World world, CubePos pos) { + for (ICubePopulator populator : population.sorted()) { + populator.prepopulate(world, pos); + } + } + + @Override + public void populate(World world, Cube cube) { + for (ICubePopulator populator : population.sorted()) { + populator.populate(world, cube); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubeTerrainGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubeTerrainGenerator.java deleted file mode 100644 index 17aa1172..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubeTerrainGenerator.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cardinalstar.cubicchunks.api.worldgen.populator; - -import net.minecraft.world.World; - -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; -import com.cardinalstar.cubicchunks.world.cube.Cube; - -public interface ICubeTerrainGenerator { - - /** - * Fills a cube with various terrain features. The cube is not in the world at this time, and no world operations - * should be performed within this method. This method is called after all vanilla generation has been performed, - * including the bottom/top chunk filling. - */ - void generate(T generator, World world, Cube cube); - -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubicPopulator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubicPopulator.java deleted file mode 100644 index b20b8e8f..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/populator/ICubicPopulator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.api.worldgen.populator; - -import net.minecraft.world.World; - -import com.cardinalstar.cubicchunks.api.worldgen.CubeGeneratorsRegistry; -import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.world.ICubicWorld; - -/** - * Implement this interface to your world generators and register them in - * {@link CubeGeneratorsRegistry} to launch them - * single time for each generated cube right after terrain and biome specific - * generators. - */ -public interface ICubicPopulator { - - /** - * Generate a specific populator feature for a given cube given a biome. - * - * To avoid requiring unnecessary amount of Cubes to do population, the population space is offset by 8 blocks in - * each direction. Instead of generating blocks only in this cube you should generate then in 16x16x16 block space - * starting from the center of current cube. - * - * Example of generating random position coordinate: - * - * {@code int x = random.nextInt(16) + 8 + pos.getXCenter();} - * - * You can also use {@link CubePos#randomPopulationPos} to generate random position in population space. - * - * All block access should be done through the provided {@link ICubicWorld} instance. - * - * @param world The {@link World} we're generating for. - * @param pos The position of the cube being populated. - * - */ - void generate(World world, CubePos pos); -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/type/VanillaCubicWorldType.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java similarity index 77% rename from src/main/java/com/cardinalstar/cubicchunks/world/type/VanillaCubicWorldType.java rename to src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java index 1edadc3a..1567e6a7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/type/VanillaCubicWorldType.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java @@ -18,42 +18,41 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cardinalstar.cubicchunks.world.type; +package com.cardinalstar.cubicchunks.api.worldtype; -import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldType; +import org.jetbrains.annotations.NotNull; + import com.cardinalstar.cubicchunks.api.IntRange; import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.BuiltinWorldDecorators; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; import com.cardinalstar.cubicchunks.world.ICubicWorldProvider; -import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; +import com.cardinalstar.cubicchunks.worldgen.VanillaWorldGenerator; @ParametersAreNonnullByDefault public class VanillaCubicWorldType extends WorldType implements ICubicWorldType { + public static VanillaCubicWorldType INSTANCE; + public static final String vanillaCubicLevelString = "VanillaCubic"; private VanillaCubicWorldType() { super(vanillaCubicLevelString); } - public static VanillaCubicWorldType create() { - return new VanillaCubicWorldType(); + public static void init() { + INSTANCE = new VanillaCubicWorldType(); } - // @Override public boolean canBeCreated() { - // return CubicChunks.DEBUG_ENABLED; - // } - - @Nullable @Override - public ICubeGenerator createCubeGenerator(World world) { - return new VanillaCompatibilityGenerator(world.provider.createChunkGenerator(), world); + public @NotNull IWorldGenerator createCubeGenerator(World world) { + return new VanillaWorldGenerator(world.provider.createChunkGenerator(), world, BuiltinWorldDecorators.CUBIC_VANILLA); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java new file mode 100644 index 00000000..babdec17 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -0,0 +1,174 @@ +package com.cardinalstar.cubicchunks.async; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import com.cardinalstar.cubicchunks.CubicChunks; +import com.cardinalstar.cubicchunks.CubicChunksConfig; +import com.google.common.util.concurrent.AbstractFuture; + +@SuppressWarnings("unused") +public class TaskPool { + + private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0); + + public static final ThreadFactory THREAD_FACTORY = runnable -> { + int number = THREAD_COUNTER.incrementAndGet(); + String name = String.format("CC BG Thread %d", number); + Thread thread = new Thread(runnable, name); + thread.setDaemon(true); + return thread; + }; + + private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor( + 1, + CubicChunksConfig.optimizations.backgroundThreads, + 60, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(1024), + THREAD_FACTORY, + new DiscardPolicy()); + + private static final ScheduledExecutorService SCHEDULED = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY); + + // static { + // SCHEDULED.scheduleAtFixedRate(TaskPool::flushLastTask, 1, 1, TimeUnit.MILLISECONDS); + // } + + // private static final Object lock = new Object(); + // private static Task lastTask; + + // private static final long TASK_BATCHING_PERIOD = Duration.ofMillis(1).toNanos(); + + public static Future submit(ITask task) { + return submit(task, null); + } + + public static Future submit(ITask task, @Nullable Consumer callback) { + long now = System.nanoTime(); + + synchronized (lock) { +// if (lastTask != null) { +// Task merged; +// +// if (lastTask.submitTime + TASK_BATCHING_PERIOD < now) { +// POOL.submit(lastTask); +// lastTask = null; +// } else if ((merged = lastTask.tryMerge(task, now, callback)) != null) { +// return merged; +// } +// } + + Task future = new Task<>(task, now, callback); +// lastTask = future; + POOL.submit(future); + return future; + } + } + + // private static void flushLastTask() { + // synchronized (lock) { + // if (lastTask != null) { + // if (lastTask.submitTime + TASK_BATCHING_PERIOD < System.nanoTime()) { + // POOL.submit(lastTask); + // lastTask = null; + // } + // } + // } + // } + + public interface ITask extends Callable { + + /// Merges another task into this one to batch their work, if possible + default boolean canMerge(ITask other) { + return false; + } + + /// Executes all merged tasks + default T callMerged(List> mergedTasks) throws Exception { + throw new UnsupportedOperationException(); + } + } + + public interface ITaskFuture { + + ITask getTask(); + void finish(T value); + void fail(Exception ex); + } + + private static class Task extends AbstractFuture implements Runnable, ITaskFuture { + + private final ITask task; + public final long submitTime; + @Nullable + private final Consumer callback; + + private List> merged = null; + + public Task(ITask task, long submitTime, @Nullable Consumer callback) { + this.task = task; + this.submitTime = submitTime; + this.callback = callback; + } + + public synchronized Task tryMerge(ITask other, long now, @Nullable Consumer callback) { + if (this.isDone()) return null; + + if (task.canMerge(other)) { + if (merged == null) merged = new ArrayList<>(1); + + Task otherTask = new Task<>(other, now, callback); + + merged.add(otherTask); + + return otherTask; + } + + return null; + } + + @Override + public synchronized void run() { + try { + if (merged != null) { + //noinspection unchecked + finish(task.callMerged((List>) (List) merged)); + } else { + finish(task.call()); + } + } catch (Exception e) { + CubicChunks.LOGGER.error("Could not run background task", e); + } + } + + @Override + public ITask getTask() { + return task; + } + + @Override + public void finish(T value) { + set(value); + if (callback != null) callback.accept(value); + } + + @Override + public void fail(Exception ex) { + setException(ex); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/client/CubeProviderClient.java b/src/main/java/com/cardinalstar/cubicchunks/client/CubeProviderClient.java index 27226421..d7047cff 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/client/CubeProviderClient.java +++ b/src/main/java/com/cardinalstar/cubicchunks/client/CubeProviderClient.java @@ -22,6 +22,8 @@ import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; +import java.util.function.Consumer; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -38,6 +40,7 @@ import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.mixin.early.client.IChunkProviderClient; import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal; @@ -88,10 +91,18 @@ public Chunk getLoadedChunk(int x, int z) { @Override public Chunk loadChunk(int cubeX, int cubeZ) { + return loadChunk(cubeX, cubeZ, null); + } + + public Chunk loadChunk(int cubeX, int cubeZ, @Nullable Consumer init) { Chunk column = new Chunk((World) this.world, cubeX, cubeZ); // make a new one + ((IColumnInternal) column).setColumn(true); + ((IChunkProviderClient) this).getChunkMapping() .add(ChunkCoordIntPair.chunkXZ2Int(cubeX, cubeZ), column); + if (init != null) init.accept(column); + // fire a forge event... make mods happy :) net.minecraftforge.common.MinecraftForge.EVENT_BUS .post(new net.minecraftforge.event.world.ChunkEvent.Load(column)); @@ -141,8 +152,8 @@ public Cube loadCube(CubePos pos) { this.cubeMap.put(cube); world.getLightingManager() .onCubeLoad(cube); - EVENT_BUS.post(new CubeEvent.Load(cube)); cube.setCubeLoaded(); + EVENT_BUS.post(new CubeEvent.Load(column.worldObj, pos, cube)); return cube; } @@ -177,10 +188,18 @@ public Cube getCube(CubePos coords) { return getCube(coords.getX(), coords.getY(), coords.getZ()); } + private volatile Cube lastCube; + @Nullable @Override public Cube getLoadedCube(int cubeX, int cubeY, int cubeZ) { - return cubeMap.get(cubeX, cubeY, cubeZ); + Cube c = lastCube; + + if (c != null && c.getX() == cubeX && c.getY() == cubeY && c.getZ() == cubeZ) { + return c; + } + + return lastCube = cubeMap.get(cubeX, cubeY, cubeZ); } @Nullable diff --git a/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java b/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java new file mode 100644 index 00000000..345038e5 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java @@ -0,0 +1,38 @@ +package com.cardinalstar.cubicchunks.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.MathHelper; +import net.minecraftforge.client.event.RenderGameOverlayEvent; + +import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +@EventBusSubscriber +public class WorldDiagnostics { + + @SubscribeEvent + public static void onRenderGameOverlayTextEvent(RenderGameOverlayEvent.Text event) { + final Minecraft mc = Minecraft.getMinecraft(); + if (mc.gameSettings.showDebugInfo) { + + int cX = Coords.blockToCube(MathHelper.floor_double(mc.thePlayer.posX)); + int cY = Coords.blockToCube(MathHelper.floor_double(mc.thePlayer.posY)); + int cZ = Coords.blockToCube(MathHelper.floor_double(mc.thePlayer.posZ)); + + Cube cube = (Cube) ((ICubicWorld) mc.theWorld).getCubeFromCubeCoords(cX, cY, cZ); + + event.left.add(""); + + event.left.add("Cube X: " + cX); + event.left.add("Cube Y: " + cY); + event.left.add("Cube Z: " + cZ); + + event.left.add(""); + + event.left.add("Cube: " + cube); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/events/ColumnEvent.java b/src/main/java/com/cardinalstar/cubicchunks/event/events/ColumnEvent.java new file mode 100644 index 00000000..1f358f3b --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/event/events/ColumnEvent.java @@ -0,0 +1,26 @@ +package com.cardinalstar.cubicchunks.event.events; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.World; +import net.minecraftforge.event.world.WorldEvent; + +public class ColumnEvent extends WorldEvent { + + public final ChunkCoordIntPair pos; + + private ColumnEvent(World world, ChunkCoordIntPair pos) { + super(world); + this.pos = pos; + } + + public static class LoadNBT extends ColumnEvent { + + public NBTTagCompound tag; + + public LoadNBT(World world, ChunkCoordIntPair pos, NBTTagCompound tag) { + super(world, pos); + this.tag = tag; + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java b/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java index a3025a34..c8aca66b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java @@ -20,24 +20,23 @@ */ package com.cardinalstar.cubicchunks.event.events; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; import net.minecraftforge.event.world.WorldEvent; -import com.cardinalstar.cubicchunks.api.ICube; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; /** * CubicChunks equivalent of {@link net.minecraftforge.event.world.ChunkEvent}. */ public class CubeEvent extends WorldEvent { - private final ICube chunk; + public final CubePos pos; - public CubeEvent(ICube cube) { - super(cube.getWorld()); - this.chunk = cube; - } - - public ICube getCube() { - return chunk; + protected CubeEvent(World world, CubePos pos) { + super(world); + this.pos = pos; } /** @@ -45,8 +44,11 @@ public ICube getCube() { */ public static class Load extends CubeEvent { - public Load(ICube cube) { - super(cube); + public final Cube cube; + + public Load(World world, CubePos pos, Cube cube) { + super(world, pos); + this.cube = cube; } } @@ -55,8 +57,32 @@ public Load(ICube cube) { */ public static class Unload extends CubeEvent { - public Unload(ICube cube) { - super(cube); + public final Cube cube; + + public Unload(World world, CubePos pos, Cube cube) { + super(world, pos); + this.cube = cube; + } + } + + public static class LoadNBT extends CubeEvent { + + public NBTTagCompound tag; + + public LoadNBT(World world, CubePos pos, NBTTagCompound tag) { + super(world, pos); + this.tag = tag; + } + } + + + public static class SaveNBT extends CubeEvent { + + public NBTTagCompound tag; + + public SaveNBT(World world, CubePos pos, NBTTagCompound tag) { + super(world, pos); + this.tag = tag; } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java index 52f698c2..35cc3e46 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java @@ -20,32 +20,21 @@ */ package com.cardinalstar.cubicchunks.event.handlers; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiCreateWorld; import net.minecraft.client.gui.GuiOptionsRowList; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiVideoSettings; import net.minecraft.client.resources.I18n; import net.minecraft.util.MathHelper; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.WorldType; -import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings; -import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; -import com.cardinalstar.cubicchunks.api.worldgen.VanillaCompatibilityGeneratorProviderBase; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.mixin.early.client.IGuiCreateWorld; import com.cardinalstar.cubicchunks.mixin.early.client.IGuiOptionsRowList; import com.cardinalstar.cubicchunks.mixin.early.client.IGuiVideoSettings; import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; @@ -205,114 +194,4 @@ public void mouseReleased(int mouseX, int mouseY) { this.dragging = false; } } - - public static class WorldSelectionCubicChunks { - - private static final int MAP_TYPE_ID = 5; - private static final int ALLOW_CHEATS_ID = 6; - private static final int CUSTOMIZE_ID = 8; - private static final int MORE_WORLD_OPTIONS = 3; - - private static final int CC_ENABLE_BUTTON_ID = 11; - private static final List LIST_OF_GEN_OPTIONS = new ArrayList(); - private static int CURRENT_GEN_OPTION = 0; - - @SubscribeEvent - public static void guiInit(InitGuiEvent.Post event) { - GuiScreen gui = event.gui; - if (isCreateWorldGui(gui)) { - init((GuiCreateWorld) gui, event.buttonList); - } - } - - private static void init(GuiCreateWorld gui, List buttons) { - if (getButton(buttons, CC_ENABLE_BUTTON_ID).isPresent()) { - return; - } - GuiButton enableCC = new GuiButton(CC_ENABLE_BUTTON_ID, 0, 0, 20, 20, "enable"); - enableCC.visible = false; - buttons.add(enableCC); - Optional customizeButton = getButton(buttons, CUSTOMIZE_ID); - Optional allowCheats = getButton(buttons, ALLOW_CHEATS_ID); - customizeButton.ifPresent(b -> allowCheats.ifPresent(c -> { - b.yPosition = c.yPosition - 21; - GuiButton mapTypeButton = getButton(buttons, MAP_TYPE_ID).get(); - enableCC.xPosition = c.xPosition; - enableCC.yPosition = b.yPosition; - enableCC.width = c.width; - enableCC.height = c.height; - enableCC.visible = mapTypeButton.visible; - - refreshText(gui, enableCC); - })); - for (VanillaCompatibilityGeneratorProviderBase base : VanillaCompatibilityGeneratorProviderBase.REGISTRY - .getAll()) { - LIST_OF_GEN_OPTIONS.add(base.registryName); - } - CURRENT_GEN_OPTION = LIST_OF_GEN_OPTIONS - .indexOf(new ResourceLocation(CubicChunksConfig.compatibilityGeneratorType)); - } - - private static void refreshText(GuiCreateWorld gui, GuiButton enableBtn) { - String txt; - if (CubicChunksConfig.forceLoadCubicChunks == CubicChunksConfig.ForceCCMode.NONE) { - txt = "cubicchunks.gui.worldmenu.cc_disable"; - } else { - VanillaCompatibilityGeneratorProviderBase provider = VanillaCompatibilityGeneratorProviderBase.REGISTRY - .get(new ResourceLocation(CubicChunksConfig.compatibilityGeneratorType)); - txt = provider.getUnlocalizedName(); - } - enableBtn.displayString = I18n.format(txt); - } - - @SubscribeEvent - public static void actionPerformed(GuiScreenEvent.ActionPerformedEvent.Post event) { - GuiScreen gui = event.gui; - GuiButton button = event.button; - if (isCreateWorldGui(gui)) { - switch (button.id) { - case MORE_WORLD_OPTIONS: { - init((GuiCreateWorld) gui, event.buttonList); - // fall through - } - case MAP_TYPE_ID: { - GuiButton enableCC = null, mapType = null; - for (GuiButton b : (List) event.buttonList) { - if (b.id == CC_ENABLE_BUTTON_ID) { - enableCC = b; - } else if (b.id == MAP_TYPE_ID) { - mapType = b; - } - } - assert enableCC != null; - boolean isCubicChunksType = WorldType.worldTypes[((IGuiCreateWorld) gui) - .getSelectedIndex()] instanceof ICubicWorldType; - enableCC.visible = mapType != null && !isCubicChunksType && mapType.visible; - break; - } - case CC_ENABLE_BUTTON_ID: { - CURRENT_GEN_OPTION++; - if (CURRENT_GEN_OPTION >= LIST_OF_GEN_OPTIONS.size()) { - CubicChunksConfig.disableCubicChunks(); - CURRENT_GEN_OPTION = -1; - } else { - CubicChunksConfig.setGenerator(LIST_OF_GEN_OPTIONS.get(CURRENT_GEN_OPTION)); - } - refreshText((GuiCreateWorld) gui, button); - break; - } - } - } - } - - private static boolean isCreateWorldGui(GuiScreen gui) { - return gui instanceof GuiCreateWorld; - } - - private static Optional getButton(List buttons, int id) { - return buttons.stream() - .filter(b -> b.id == id) - .findFirst(); - } - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index a1a1b7a0..a708258c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -68,6 +68,14 @@ public enum Mixins implements IMixins { .addCommonMixins("common.vanillaclient.MixinS01PacketJoinGame") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_OVERWORLD_GENERATOR(new MixinBuilder("Modify overworld chunk generator") + .addCommonMixins("common.worldgen.MixinChunkProviderGenerate") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), + MIXIN_EBS(new MixinBuilder("Add simple cache to ExtendedBlockStorage.getBlockByExtId") + .addCommonMixins("common.MixinExtendedBlockStorage") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), // CHUNK MIXIN_CHUNK_COLUMN( @@ -162,6 +170,10 @@ public enum Mixins implements IMixins { new MixinBuilder("Fixing mobs walking off into chasms below y = 0").addCommonMixins("common.MixinPathFinder") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_ENTITY_BRIGHTNESS(new MixinBuilder("Fix Entity.getBrightness") + .addCommonMixins("common.MixinEntity_Brightness") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), // SERVER MIXIN_INTEGRATED_SERVER_ACCESSOR( diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java index eaa34160..bdd8634c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java @@ -84,27 +84,30 @@ public boolean disableVoidFog(WorldProvider instance) { ordinal = 1)) public float modifyVoidFog(EntityRenderer instance, Operation original, @Local(argsOnly = true) float partialTicks) { - float farPlaneDistance = original.call(instance); - if (!this.mc.theWorld.provider.getWorldHasVoidParticles()) { - return farPlaneDistance; - } - - EntityLivingBase player = this.mc.renderViewEntity; - - int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; - double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double) partialTicks; - - double fogStrength = skylight / 16.0D + Math.max(playerY / 32.0D, 0.25); - - if (fogStrength < 1.0D) { - if (fogStrength < 0.0D) { - fogStrength = 0.0D; - } - - farPlaneDistance *= (float) (fogStrength * fogStrength); - } - - return farPlaneDistance; + return 1000f; + // Temporarily disable this - it breaks angelica +// float farPlaneDistance = original.call(instance); +// +// if (!this.mc.theWorld.provider.getWorldHasVoidParticles()) { +// return farPlaneDistance; +// } +// +// EntityLivingBase player = this.mc.renderViewEntity; +// +// int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; +// double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double) partialTicks; +// +// double fogStrength = skylight / 16.0D + Math.max(playerY / 32.0D, 0.25); +// +// if (fogStrength < 1.0D) { +// if (fogStrength < 0.0D) { +// fogStrength = 0.0D; +// } +// +// farPlaneDistance *= (float) (fogStrength * fogStrength); +// } +// +// return farPlaneDistance; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java index 21720c49..4e1b83c8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java @@ -35,7 +35,6 @@ import com.cardinalstar.cubicchunks.api.IntRange; import com.cardinalstar.cubicchunks.client.CubeProviderClient; -import com.cardinalstar.cubicchunks.lighting.LightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.mixin.early.common.MixinWorld; import com.llamalad7.mixinextras.expression.Definition; @@ -57,7 +56,6 @@ public void initCubicWorldClient(IntRange heightRange, IntRange generationRange) CubeProviderClient cubeProviderClient = new CubeProviderClient(this); this.chunkProvider = cubeProviderClient; this.clientChunkProvider = cubeProviderClient; - this.lightingManager = new LightingManager((World) (Object) this); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java index 0d0ef9b7..2f7e2188 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java @@ -251,7 +251,6 @@ private void cubicChunkColumn_construct(World world, int x, int z, CallbackInfo // Some mods construct chunks with null world, ignore them return; } - this.isColumn = true; // this.lightManager = world.getLightingManager(); this.cubeMap = new CubeMap(); @@ -728,8 +727,10 @@ private void onGetBlockLightValue(int x, int y, int z, int amount, CallbackInfoR if (!isColumn) { return; } - getWorldObj().getLightingManager() - .onGetLightSubtracted(x, y, z); + if (getWorldObj().getLightingManager() != null) { + getWorldObj().getLightingManager() + .onGetLightSubtracted(x, y, z); + } } // ============================================== diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinEntity_Brightness.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinEntity_Brightness.java new file mode 100644 index 00000000..f9ffe0ea --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinEntity_Brightness.java @@ -0,0 +1,21 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.entity.Entity; +import net.minecraft.util.MathHelper; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +@Mixin(Entity.class) +public class MixinEntity_Brightness { + + @Shadow + public double posY; + + @ModifyConstant(method = "getBrightness", constant = @Constant(intValue = 0)) + private int modifyY(int constant) { + return MathHelper.floor_double(this.posY); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java new file mode 100644 index 00000000..f17ef579 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java @@ -0,0 +1,27 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ExtendedBlockStorage.class) +public class MixinExtendedBlockStorage { + + @Unique + private Block prevBlock = Blocks.air; + @Unique + private int prevId = 0; + + @Redirect(method = "getBlockByExtId", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;getBlockById(I)Lnet/minecraft/block/Block;")) + public Block optimizeGetBlock(int id) { + if (id == prevId) return prevBlock; + + prevId = id; + return prevBlock = Block.getBlockById(id); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java index 05e0d0d5..bbdadc11 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java @@ -51,7 +51,6 @@ import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.MapStorage; import net.minecraft.world.storage.WorldInfo; -import net.minecraftforge.common.DimensionManager; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Implements; @@ -64,11 +63,11 @@ import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.cardinalstar.cubicchunks.CubicChunks; -import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.IntRange; import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; @@ -77,16 +76,14 @@ import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.ReflectionUtil; +import com.cardinalstar.cubicchunks.world.CubicChunksSavedData; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.ICubicWorldProvider; -import com.cardinalstar.cubicchunks.world.WorldSavedCubicChunksData; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.ICubeProvider; import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal; import com.google.common.collect.ImmutableList; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; -import com.llamalad7.mixinextras.expression.Definition; -import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.sugar.Local; /** @@ -217,89 +214,49 @@ public abstract class MixinWorld implements ICubicWorldInternal { @Shadow protected abstract IChunkProvider createChunkProvider(); - @Definition(id = "isInitialized", method = "Lnet/minecraft/world/storage/WorldInfo;isInitialized()Z") - @Definition( - id = "worldInfo", - field = "Lnet/minecraft/world/World;worldInfo:Lnet/minecraft/world/storage/WorldInfo;") - @Expression("this.worldInfo.isInitialized()") + @Redirect( + method = "(Lnet/minecraft/world/storage/ISaveHandler;Ljava/lang/String;Lnet/minecraft/world/WorldSettings;Lnet/minecraft/world/WorldProvider;Lnet/minecraft/profiler/Profiler;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;createChunkProvider()Lnet/minecraft/world/chunk/IChunkProvider;")) + public IChunkProvider noopCreateProvider(World instance) { + // Done below manually + return null; + } + @Inject( method = "(Lnet/minecraft/world/storage/ISaveHandler;Ljava/lang/String;Lnet/minecraft/world/WorldSettings;Lnet/minecraft/world/WorldProvider;Lnet/minecraft/profiler/Profiler;)V", - at = @At("MIXINEXTRAS:EXPRESSION")) + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/storage/WorldInfo;isInitialized()Z")) public void initWorld(ISaveHandler p_i45369_1_, String p_i45369_2_, WorldSettings p_i45369_3_, WorldProvider p_i45369_4_, Profiler p_i45369_5_, CallbackInfo ci) { - if ((Object) this instanceof WorldServer) { - ((ICubicWorldInternal.Server) this).initCubicWorldServer(); - } - WorldSavedCubicChunksData savedData = (WorldSavedCubicChunksData) this.perWorldStorage - .loadData(WorldSavedCubicChunksData.class, "cubicChunksData"); - boolean ccWorldType = this.worldInfo.getTerrainType() instanceof ICubicWorldType; - boolean ccGenerator = ccWorldType - && ((ICubicWorldType) this.worldInfo.getTerrainType()).hasCubicGeneratorForWorld((World) (Object) this); - boolean savedCC = savedData != null && savedData.isCubicChunks; - boolean ccWorldInfo = (savedData == null || savedData.isCubicChunks); - boolean excludeCC = CubicChunksConfig.isDimensionExcluded(this.provider.dimensionId); - boolean forceExclusions = CubicChunksConfig.forceDimensionExcludes; - // TODO: simplify this mess of booleans and document where each of them comes from - // these espressions are generated using Quine McCluskey algorithm - // using the JQM v1.2.0 (Java QuineMcCluskey) program: - // IS_CC := CC_GEN OR CC_TYPE AND NOT(EXCLUDED) OR SAVED_CC AND NOT(EXCLUDED) OR SAVED_CC AND NOT(F_EX) OR - // CC_NEW AND NOT(EXCLUDED); - // ERROR := CC_GEN AND NOT(CC_TYPE); - boolean impossible = ccGenerator && !ccWorldType; - if (impossible) { - throw new Error("Trying to use cubic chunks generator without cubic chunks world type."); - } - boolean isCC = ccGenerator || (ccWorldType && !excludeCC) - || (savedCC && !excludeCC) - || (savedCC && !forceExclusions) - || (ccWorldInfo && !excludeCC); - if ((CubicChunksConfig.forceLoadCubicChunks == CubicChunksConfig.ForceCCMode.LOAD_NOT_EXCLUDED && !excludeCC) - || CubicChunksConfig.forceLoadCubicChunks == CubicChunksConfig.ForceCCMode.ALWAYS) { - isCC = true; - } - if (savedData == null) { - int minY = CubicChunksConfig.defaultMinHeight; - int maxY = CubicChunksConfig.defaultMaxHeight; - if (this.provider.dimensionId != 0) { - WorldSavedCubicChunksData overworld = (WorldSavedCubicChunksData) DimensionManager - .getWorld(0).perWorldStorage.loadData(WorldSavedCubicChunksData.class, "cubicChunksData"); - if (overworld != null) { - minY = overworld.minHeight; - maxY = overworld.maxHeight; - } - } - savedData = new WorldSavedCubicChunksData("cubicChunksData", isCC, minY, maxY); - } - savedData.markDirty(); - this.perWorldStorage.setData("cubicChunksData", savedData); - this.perWorldStorage.saveAllData(); + // Some other world instantiation that we don't care about (fake dummy worlds, for instance) + //noinspection ConstantValue + if (!((Object) this instanceof WorldServer worldServer)) return; - if (!isCC) { - return; - } + ((ICubicWorldInternal.Server) this).initCubicWorldServer(); - if (shouldSkipWorld((World) (Object) this)) { + if (shouldSkipWorld(worldServer)) { CubicChunks.LOGGER.info( - "Skipping world " + this - + " with type " - + this.worldInfo.getTerrainType() - + " due to potential " - + "compatibility issues"); + "Skipping world {} with type {} due to potential compatibility issues", + this, + this.worldInfo.getTerrainType()); return; } - CubicChunks.LOGGER.info("Initializing world " + this + " with type " + this.worldInfo.getTerrainType()); + + CubicChunks.LOGGER.info("Initializing world {} with type {}", this, this.worldInfo.getTerrainType()); IntRange generationRange = new IntRange(0, ((ICubicWorldProvider) this.provider).getOriginalActualHeight()); + WorldType type = this.worldInfo.getTerrainType(); - if (type instanceof ICubicWorldType - && ((ICubicWorldType) type).hasCubicGeneratorForWorld((World) (Object) this)) { - generationRange = ((ICubicWorldType) type).calculateGenerationHeightRange((WorldServer) (Object) this); + + if (type instanceof ICubicWorldType && ((ICubicWorldType) type).hasCubicGeneratorForWorld(worldServer)) { + generationRange = ((ICubicWorldType) type).calculateGenerationHeightRange(worldServer); } - int minHeight = savedData.minHeight; - int maxHeight = savedData.maxHeight; - this.initCubicWorld(new IntRange(minHeight, maxHeight), generationRange); + this.chunkProvider = createChunkProvider(); + + CubicChunksSavedData savedData = CubicChunksSavedData.get(worldServer); + + this.initCubicWorld(new IntRange(savedData.minHeight, savedData.maxHeight), generationRange); } protected void initCubicWorld(IntRange heightRange, IntRange generationRange) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java index b56489aa..9095e9e4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java @@ -27,6 +27,7 @@ import net.minecraft.util.ChunkCoordinates; import net.minecraft.world.World; import net.minecraft.world.WorldProvider; +import net.minecraft.world.WorldType; import net.minecraft.world.chunk.IChunkProvider; import org.spongepowered.asm.mixin.Mixin; @@ -36,12 +37,13 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import com.cardinalstar.cubicchunks.api.util.NotCubicChunksWorldException; import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.BuiltinWorldDecorators; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.ICubicWorldProvider; import com.cardinalstar.cubicchunks.world.SpawnPlaceFinder; +import com.cardinalstar.cubicchunks.worldgen.VanillaWorldGenerator; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; @ParametersAreNonnullByDefault @@ -93,15 +95,17 @@ public int getOriginalActualHeight() { @Nullable @Override - public ICubeGenerator createCubeGenerator() { - if (worldObj.getWorldInfo() - .getTerrainType() instanceof ICubicWorldType - && ((ICubicWorldType) worldObj.getWorldInfo() - .getTerrainType()).hasCubicGeneratorForWorld(worldObj)) { - return ((ICubicWorldType) worldObj.getWorldInfo() - .getTerrainType()).createCubeGenerator(worldObj); + public IWorldGenerator createCubeGenerator() { + WorldType terrainType = worldObj.getWorldInfo().getTerrainType(); + + if (terrainType instanceof ICubicWorldType ccWorldType && ccWorldType.hasCubicGeneratorForWorld(worldObj)) { + return ccWorldType.createCubeGenerator(worldObj); } - throw new NotCubicChunksWorldException(); + + return new VanillaWorldGenerator( + worldObj.provider.createChunkGenerator(), + worldObj, + BuiltinWorldDecorators.VANILLA); } @Inject(method = "getRandomizedSpawnPoint", at = @At(value = "HEAD"), cancellable = true, remap = false) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java index e300955d..570b1705 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java @@ -23,6 +23,7 @@ import static com.cardinalstar.cubicchunks.util.Coords.cubeToMinBlock; import static com.cardinalstar.cubicchunks.util.ReflectionUtil.cast; +import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -61,6 +62,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; @@ -79,6 +81,7 @@ import com.cardinalstar.cubicchunks.world.ISpawnerAnimals; import com.cardinalstar.cubicchunks.world.chunkloader.CubicChunkManager; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.savedata.WorldFormatSavedData; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @@ -164,6 +167,16 @@ private ChunkProviderServer redirectChunkProviderServer(WorldServer world, IChun return new CubeProviderServer(world, chunkLoader, ((ICubicWorldProvider) world.provider).createCubeGenerator()); } + @Inject(method = "getChunkSaveLocation", at = @At("HEAD"), cancellable = true, remap = false) + private void redirectChunkSaveLocation(CallbackInfoReturnable cir) { + if (this.theChunkProviderServer == null) { + WorldFormatSavedData format = WorldFormatSavedData.get((WorldServer) (Object) this); + + //noinspection DataFlowIssue + cir.setReturnValue(format.getFormat().getWorldSaveDirectory(this.saveHandler, (WorldServer) (Object) this).toFile()); + } + } + @WrapOperation( method = { "scheduleBlockUpdateWithPriority", "func_147446_b" }, at = @At(value = "INVOKE", target = "Ljava/util/TreeSet;add(Ljava/lang/Object;)Z", remap = false)) @@ -423,4 +436,11 @@ private void tickColumn(boolean raining, boolean thundering, Chunk chunk) { } this.theProfiler.endSection(); } + + /// Immediate block updates rarely work well in CC. Vanilla expects there to be a hard limit to the number of steps + /// something can take, but CC removes many of those limits so it's better to just disable the feature for now. + @Redirect(method = "scheduleBlockUpdateWithPriority", at = @At(value = "FIELD", target = "Lnet/minecraft/world/WorldServer;scheduledUpdatesAreImmediate:Z")) + private boolean disableImmediateBlockUpdates(WorldServer instance) { + return false; + } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java index c5d0e408..e961eda2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java @@ -532,4 +532,9 @@ private boolean isValidForRendering(Chunk column, @Local(argsOnly = true, ordina return cube.isPopulated(); } + + @Redirect(method = "getBiomeGenForCoordsBody", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;blockExists(III)Z")) + private boolean checkColumnExists(World instance, int x, int zero, int z) { + return ((ICubicWorld) instance).getCubeCache().getLoadedColumn(x >> 4, z >> 4) != null; + } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_Tick.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_Tick.java index 64f6399e..e7390002 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_Tick.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_Tick.java @@ -23,6 +23,7 @@ import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.MathHelper; import net.minecraft.world.World; @@ -69,6 +70,9 @@ public abstract class MixinWorld_Tick implements ICubicWorld { private boolean canUpdateEntity(World _this, int startBlockX, int oldStartBlockY, int startBlockZ, int endBlockX, int oldEndBlockY, int endBlockZ, @Local(argsOnly = true) Entity entity) { + // Prevent player rubberbanding + if (entity instanceof EntityPlayer) return true; + int entityPosY = MathHelper.floor_double(entity.posY); int entityPosX = MathHelper.floor_double(entity.posX); int entityPosZ = MathHelper.floor_double(entity.posZ); diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java index 7f84b258..c903ca60 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java @@ -1,20 +1,45 @@ package com.cardinalstar.cubicchunks.mixin.early.common.worldgen; import net.minecraft.block.Block; +import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.World; import net.minecraft.world.chunk.IChunkProvider; import net.minecraft.world.gen.ChunkProviderGenerate; import net.minecraft.world.gen.MapGenBase; +import net.minecraft.world.gen.NoiseGeneratorOctaves; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +import com.cardinalstar.cubicchunks.api.world.Precalculable; +import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalculableNoise; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @Mixin(ChunkProviderGenerate.class) -public class MixinChunkProviderGenerate { +public class MixinChunkProviderGenerate implements Precalculable { + + @Shadow + private NoiseGeneratorOctaves field_147431_j; + + @Shadow + private NoiseGeneratorOctaves field_147432_k; + + @Shadow + private NoiseGeneratorOctaves field_147429_l; + + @Shadow + public NoiseGeneratorOctaves noiseGen6; + +// @Redirect(method = "", at = @At(value = "NEW", target = "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) +// public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves) { +// return new PrecalcedVanillaOctaves(random, octaves); +// } @Definition( id = "caveGenerator", @@ -27,4 +52,33 @@ public class MixinChunkProviderGenerate { public void noopCaveGen(MapGenBase instance, IChunkProvider i2, World k1, int j1, int i, Block[] p_151539_1_) { } + + @Unique + private final LongArrayFIFOQueue chunkGenFIFO = new LongArrayFIFOQueue(); + @Unique + private final LongOpenHashSet chunkGenDebounce = new LongOpenHashSet(); + + @Override + public void precalculate(int cubeX, int cubeY, int cubeZ) { + long coord = ChunkCoordIntPair.chunkXZ2Int(cubeX, cubeZ); + + synchronized (this) { + if (chunkGenDebounce.add(coord)) { + chunkGenFIFO.enqueue(coord); + + while (chunkGenFIFO.size() > 1024) { + long chunk = chunkGenFIFO.dequeueLong(); + chunkGenDebounce.remove(chunk); + } + } + } + + int noiseX = cubeX * 4; + int noiseZ = cubeZ * 4; + + if (this.field_147431_j instanceof PrecalculableNoise precalc) precalc.precalculate(noiseX, 0, noiseZ); + if (this.field_147432_k instanceof PrecalculableNoise precalc) precalc.precalculate(noiseX, 0, noiseZ); + if (this.field_147429_l instanceof PrecalculableNoise precalc) precalc.precalculate(noiseX, 0, noiseZ); + if (this.noiseGen6 instanceof PrecalculableNoise precalc) precalc.precalculate(noiseX, 10, noiseZ); + } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/server/MixinDedicatedServer_DefaultLevelType.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/server/MixinDedicatedServer_DefaultLevelType.java index 23ec12b1..99ae7707 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/server/MixinDedicatedServer_DefaultLevelType.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/server/MixinDedicatedServer_DefaultLevelType.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyConstant; -import com.cardinalstar.cubicchunks.world.type.VanillaCubicWorldType; +import com.cardinalstar.cubicchunks.api.worldtype.VanillaCubicWorldType; @Mixin(DedicatedServer.class) public class MixinDedicatedServer_DefaultLevelType { diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java index 17860568..aa8fbf9c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java @@ -79,11 +79,11 @@ public void process(World world, PacketColumn packet) { ICubicWorld worldClient = (ICubicWorld) world; CubeProviderClient cubeCache = (CubeProviderClient) worldClient.getCubeCache(); - Chunk column = cubeCache.loadChunk(packet.chunkX, packet.chunkZ); + cubeCache.loadChunk(packet.chunkX, packet.chunkZ, column -> { + ByteBuf buf = Unpooled.wrappedBuffer(packet.data); - ByteBuf buf = Unpooled.wrappedBuffer(packet.data); - - WorldEncoder.decodeColumn(new CCPacketBuffer(buf), column); + WorldEncoder.decodeColumn(new CCPacketBuffer(buf), column); + }); if (AngelicaInterop.hasDelegate()) { AngelicaInterop.getDelegate().onColumnLoaded(packet.chunkX, packet.chunkZ); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index 1ce6488c..90c00721 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -56,7 +56,6 @@ public byte getPacketID() { public PacketEncoderCubes() {} public static PacketCubes createPacket(List cubes) { - CubicChunks.LOGGER.info("Sending packet with {} cubes", cubes.size()); cubes.sort( Comparator.comparingInt(Cube::getY) .thenComparingInt(Cube::getX) diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java index c4a7e78e..57b6f0a9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java @@ -74,7 +74,7 @@ public void process(World world, PacketUnloadCube packet) { cubeCache.unloadCube(packet.pos); if (AngelicaInterop.hasDelegate()) { - AngelicaInterop.getDelegate().onCubeLoaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); + AngelicaInterop.getDelegate().onCubeUnloaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistryManager.java b/src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistryManager.java deleted file mode 100644 index 949fdb42..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/registry/CubicChunksRegistryManager.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cardinalstar.cubicchunks.registry; - -import java.util.HashMap; -import java.util.Map; - -public class CubicChunksRegistryManager { - - private final Map, CubicChunksRegistry> registries = new HashMap<>(); - - public void registerRegistry(Class clazz, CubicChunksRegistry registry) { - registries.put(clazz, registry); - } - - @SuppressWarnings("unchecked") - public CubicChunksRegistry getRegistry(Class clazz) { - return (CubicChunksRegistry) registries.get(clazz); - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/registry/ICubicChunksRegistryEntry.java b/src/main/java/com/cardinalstar/cubicchunks/registry/ICubicChunksRegistryEntry.java deleted file mode 100644 index 158c9fb6..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/registry/ICubicChunksRegistryEntry.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cardinalstar.cubicchunks.registry; - -import javax.annotation.Nullable; - -import net.minecraft.util.ResourceLocation; - -/** - * This class is basically a simplified recreation of the forge registry system from 1.12. I liked how it works - * and keeps things organized, so I wanted to do it as well. - * - * @param The base class of the registries. - */ -public interface ICubicChunksRegistryEntry { - - V setRegistryName(ResourceLocation name); - - @Nullable - ResourceLocation getRegistryName(); - - Class getRegistryType(); -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java b/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java index 0c8fa2a4..01ce9aba 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java @@ -44,7 +44,6 @@ import com.cardinalstar.cubicchunks.util.BucketSorterEntry; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.XZAddressable; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; @ParametersAreNonnullByDefault public class ColumnWatcher implements XZAddressable, BucketSorterEntry, IColumnWatcher { @@ -63,24 +62,16 @@ public class ColumnWatcher implements XZAddressable, BucketSorterEntry, IColumnW public boolean isSentToPlayers; - private CubeProviderServer.EagerColumnLoadRequest request; - public ColumnWatcher(CubicPlayerManager cubicPlayerManager, ChunkCoordIntPair pos) { this.cubicPlayerManager = cubicPlayerManager; this.pos = pos; this.cubeCache = ((ICubicWorldInternal.Server) cubicPlayerManager.getWorldServer()).getCubeCache(); this.column = cubeCache.getLoadedColumn(pos.chunkXPos, pos.chunkZPos); - - if (column == null) { - request = cubeCache - .loadColumnEagerly(pos.chunkXPos, pos.chunkZPos, ICubeProviderServer.Requirement.GENERATE); - } } public void onColumnLoaded(Chunk column) { this.column = column; - request = null; } // CHECKED: 1.10.2-12.18.1.2092 @@ -114,10 +105,6 @@ public void addPlayer(EntityPlayerMP player) { public void removePlayer(EntityPlayerMP player) { if (!playersWatchingChunk.remove(player)) return; - if (request != null) { - request.cancel(); - } - if (this.isSentToPlayers) { PacketEncoderUnloadColumn.createPacket(pos.chunkXPos, pos.chunkZPos) .sendToPlayer(player); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index 1fc876a4..c1c9e1f5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -41,7 +41,6 @@ import net.minecraft.profiler.Profiler; import net.minecraft.util.IProgressUpdate; import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.chunk.Chunk; @@ -53,25 +52,23 @@ import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.XYZAddressable; -import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatProviderBase; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; +import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderCallback; import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderServer; import com.cardinalstar.cubicchunks.server.chunkio.ICubeLoader; import com.cardinalstar.cubicchunks.util.BucketSorterEntry; import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.WatchersSortingList2D; import com.cardinalstar.cubicchunks.util.WatchersSortingList3D; -import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; import com.cardinalstar.cubicchunks.world.column.EmptyColumn; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal; +import com.cardinalstar.cubicchunks.world.savedata.WorldFormatSavedData; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; - import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; /** @@ -99,7 +96,7 @@ public class CubeProviderServer extends ChunkProviderServer // @Nonnull private final CubePrimer cubePrimer; @Nonnull - private final ICubeGenerator cubeGen; + private final IWorldGenerator worldGenerator; @Nonnull private final Profiler profiler; @@ -107,10 +104,6 @@ public class CubeProviderServer extends ChunkProviderServer 0, this::getPlayers); - private final WatchersSortingList2D eagerColumnLoads = new WatchersSortingList2D<>( - 0, - this::getPlayers); - private int columnsLoadedThisTick = 0; private int cubesLoadedThisTick = 0; @@ -122,7 +115,7 @@ public class CubeProviderServer extends ChunkProviderServer private final ObjectLinkedOpenHashSet callbacks = new ObjectLinkedOpenHashSet<>(); - public CubeProviderServer(WorldServer worldServer, IChunkLoader chunkLoader, ICubeGenerator cubeGen) { + public CubeProviderServer(WorldServer worldServer, IChunkLoader chunkLoader, IWorldGenerator worldGenerator) { super( worldServer, chunkLoader, // forge uses this in @@ -130,26 +123,24 @@ public CubeProviderServer(WorldServer worldServer, IChunkLoader chunkLoader, ICu // may be enough // this.cubePrimer = new CubePrimer(); - this.cubeGen = cubeGen; + this.worldGenerator = worldGenerator; this.worldServer = worldServer; this.profiler = worldServer.theProfiler; try { Path path = worldServer.getSaveHandler() .getWorldDirectory() .toPath(); + if (worldServer.provider.getSaveFolder() != null) { path = path.resolve(worldServer.provider.getSaveFolder()); } - // use the save format stored in the server's default world as the global world storage type - // TODO THIS IS DEFINITELY WRONG RIGHT NOW - World overworld = worldServer.provider.worldObj; + WorldFormatSavedData format = WorldFormatSavedData.get(worldServer); this.cubeLoader = new CubeLoaderServer( worldServer, - StorageFormatProviderBase.REGISTRY.get(StorageFormatProviderBase.DEFAULT) - .provideStorage(worldServer, path), - cubeGen, + format.getFormat().provideStorage(worldServer, path), + worldGenerator, new LoadingCallbacks()); } catch (IOException e) { throw new UncheckedIOException(e); @@ -194,7 +185,7 @@ public void onCubeLoaded(Cube cube) { } @Override - public void onCubeGenerated(Cube cube, CubeLoaderServer.CubeInitLevel newLevel) { + public void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { cubesLoadedThisTick++; callbacks.forEach(c -> c.onCubeGenerated(cube, newLevel)); @@ -271,7 +262,6 @@ public Chunk provideColumn(int cubeX, int cubeZ) { } @Override - @Deprecated public Chunk provideChunk(int cubeX, int cubeZ) { return provideColumn(cubeX, cubeZ); } @@ -318,14 +308,6 @@ public void tick() { profiler.endSection(); - if (columnsLoadedThisTick > 0) { - CubicChunks.LOGGER.info("Loaded {} columns this tick", columnsLoadedThisTick); - } - - if (cubesLoadedThisTick > 0) { - CubicChunks.LOGGER.info("Loaded {} cubes this tick", cubesLoadedThisTick); - } - doEagerLoading(); columnsLoadedThisTick = 0; @@ -335,27 +317,12 @@ public void tick() { private void doEagerLoading() { profiler.startSection("Eager object sorting"); - eagerColumnLoads.tick(); eagerCubeLoads.tick(); profiler.endStartSection("Eager object loading"); - int columns = 0, cubes = 0; - - Iterator colIter = eagerColumnLoads.iterator(); - long start = System.nanoTime(); - while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && colIter.hasNext()) { - EagerColumnLoadRequest request = colIter.next(); - colIter.remove(); - request.completed = true; - - cubeLoader.getColumn(request.pos.chunkXPos, request.pos.chunkZPos, request.effort); - - columns++; - } - Iterator cubeIter = eagerCubeLoads.iterator(); while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { @@ -363,11 +330,15 @@ private void doEagerLoading() { cubeIter.remove(); request.completed = true; + cubeLoader.pauseLoadCalls(); + Cube cube = cubeLoader.getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); - CubeLoaderServer.CubeInitLevel actual = cube == null ? CubeLoaderServer.CubeInitLevel.None + cubeLoader.unpauseLoadCalls(); + + CubeInitLevel actual = cube == null ? CubeInitLevel.None : cube.getInitLevel(); - CubeLoaderServer.CubeInitLevel wanted = CubeLoaderServer.CubeInitLevel.fromRequirement(request.effort); + CubeInitLevel wanted = CubeInitLevel.fromRequirement(request.effort); if (actual.ordinal() < wanted.ordinal()) { CubicChunks.LOGGER.error( @@ -378,8 +349,6 @@ private void doEagerLoading() { wanted, actual); } - - cubes++; } long delta = System.nanoTime() - start; @@ -388,14 +357,6 @@ private void doEagerLoading() { CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); } - if (columns > 0) { - CubicChunks.LOGGER.info("Processed {} eager column loads this tick", columns); - } - - if (cubes > 0) { - CubicChunks.LOGGER.info("Processed {} eager cube loads this tick", cubes); - } - profiler.endSection(); } @@ -433,7 +394,7 @@ public String makeString() { @Override public List getPossibleCreatures(EnumCreatureType type, int x, int y, int z) { - return cubeGen.getPossibleCreatures(type, x, y, z); + return worldGenerator.getPossibleCreatures(type, x, y, z); } // getLoadedChunkCount() in ChunkProviderServer is fine - CHECKED: 1.10.2-12.18.1.2092 @@ -469,74 +430,6 @@ public Cube getLoadedCube(CubePos coords) { return getLoadedCube(coords.getX(), coords.getY(), coords.getZ()); } - @SuppressWarnings("unused") - public class EagerColumnLoadRequest implements XZAddressable, BucketSorterEntry { - - public final ChunkCoordIntPair pos; - private Requirement effort; - private boolean completed; - - public EagerColumnLoadRequest(int cubeX, int cubeZ, Requirement effort) { - this.pos = new ChunkCoordIntPair(cubeX, cubeZ); - this.effort = effort; - } - - public Requirement getEffort() { - return effort; - } - - public void setEffort(Requirement effort) { - this.effort = effort; - } - - public boolean isCompleted() { - return completed; - } - - public void cancel() { - CubeProviderServer.this.eagerColumnLoads.remove(this); - } - - @Override - public final boolean equals(Object o) { - if (!(o instanceof EagerColumnLoadRequest that)) return false; - - return pos.equals(that.pos); - } - - @Override - public int hashCode() { - return pos.hashCode(); - } - - @Override - public String toString() { - return "EagerCubeLoadRequest{" + "pos=" + pos + '}'; - } - - private long[] bucketDataEntry = null; - - @Override - public long[] getBucketEntryData() { - return bucketDataEntry; - } - - @Override - public void setBucketEntryData(long[] data) { - bucketDataEntry = data; - } - - @Override - public int getX() { - return pos.chunkXPos; - } - - @Override - public int getZ() { - return pos.chunkZPos; - } - } - @SuppressWarnings("unused") public class EagerCubeLoadRequest implements XYZAddressable, BucketSorterEntry { @@ -544,8 +437,8 @@ public class EagerCubeLoadRequest implements XYZAddressable, BucketSorterEntry { private Requirement effort; private boolean completed; - public EagerCubeLoadRequest(int cubeX, int cubeY, int cubeZ, Requirement effort) { - this.pos = new CubePos(cubeX, cubeY, cubeZ); + public EagerCubeLoadRequest(CubePos pos, Requirement effort) { + this.pos = pos; this.effort = effort; } @@ -610,19 +503,15 @@ public int getZ() { } } - public EagerColumnLoadRequest loadColumnEagerly(int x, int z, Requirement effort) { - EagerColumnLoadRequest request = new EagerColumnLoadRequest(x, z, effort); - - eagerColumnLoads.add(request); - - return request; - } - public EagerCubeLoadRequest loadCubeEagerly(int x, int y, int z, Requirement effort) { - EagerCubeLoadRequest request = new EagerCubeLoadRequest(x, y, z, effort); + CubePos pos = new CubePos(x, y, z); + + EagerCubeLoadRequest request = new EagerCubeLoadRequest(pos, effort); eagerCubeLoads.add(request); + cubeLoader.preloadCube(pos, CubeInitLevel.fromRequirement(effort)); + return request; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java index f72fdc84..48e33551 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java @@ -39,7 +39,7 @@ import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.network.PacketEncoderCubeBlockChange; import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadCube; -import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderServer; +import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.BucketSorterEntry; import com.cardinalstar.cubicchunks.util.CubePos; @@ -79,7 +79,7 @@ public class CubeWatcher implements ITicket, ICubeWatcher, BucketSorterEntry { Cube loaded = cubeCache.getLoadedCube(cubePos); - if (loaded != null && loaded.isInitializedToLevel(CubeLoaderServer.CubeInitLevel.Lit)) { + if (loaded != null && loaded.isInitializedToLevel(CubeInitLevel.Lit)) { onCubeLoaded(loaded); } else { request = cubeCache @@ -89,8 +89,8 @@ public class CubeWatcher implements ITicket, ICubeWatcher, BucketSorterEntry { public void onCubeLoaded(Cube c) { if (this.invalid) return; - if (c.getInitLevel() != CubeLoaderServer.CubeInitLevel.Lit) { - if (request != null && request.isCompleted()) { + if (c.getInitLevel() != CubeInitLevel.Lit) { + if (request == null || request.isCompleted()) { request = cubeCache.loadCubeEagerly( cubePos.getX(), cubePos.getY(), @@ -100,9 +100,12 @@ public void onCubeLoaded(Cube c) { return; } + this.cube = c; this.cube.getTickets() .add(this); + + if (this.request != null) this.request.cancel(); this.request = null; } @@ -188,9 +191,7 @@ public boolean isSentToPlayers() { } public boolean isWaitingForCube() { - return this.cube == null || !this.cube.isPopulated() - || !this.cube.isInitialLightingDone() - || !this.cube.isSurfaceTracked(); + return this.cube == null || this.cube.getInitLevel() != CubeInitLevel.Lit; } public boolean isWaitingForColumn() { @@ -273,7 +274,9 @@ void update() { if (!this.players.isEmpty()) { if (this.dirtyBlocks.size() >= ForgeModContainer.clumpingThreshold) { // send whole cube - this.players.forEach(entry -> cubicPlayerManager.scheduleSendCubeToPlayer(cube, entry)); + for (EntityPlayerMP entry : this.players) { + cubicPlayerManager.scheduleSendCubeToPlayer(cube, entry); + } } else { // send all the dirty blocks var packet = PacketEncoderCubeBlockChange.createPacket(this.cube, this.dirtyBlocks); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index e2f0b6ec..4252ae63 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -58,8 +58,8 @@ import com.cardinalstar.cubicchunks.entity.ICubicEntityTracker; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.network.PacketEncoderCubes; +import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderCallback; -import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderServer; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.WatchersSortingList2D; import com.cardinalstar.cubicchunks.util.WatchersSortingList3D; @@ -434,7 +434,7 @@ public void onCubeLoaded(Cube cube) { } @Override - public void onCubeGenerated(Cube cube, CubeLoaderServer.CubeInitLevel newLevel) { + public void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { CubeWatcher watcher = this.cubeWatchers.get(cube); if (watcher != null) { @@ -503,7 +503,7 @@ private ColumnWatcher getOrCreateColumnWatcher(ChunkCoordIntPair chunkPos) { // CHECKED: 1.10.2-12.18.1.2092 @Override public void markBlockForUpdate(int x, int y, int z) { - CubeWatcher cubeWatcher = this.getCubeWatcher(CubePos.fromBlockCoords(x, y, z)); + CubeWatcher cubeWatcher = this.getCubeWatcher(x >> 4, y >> 4, z >> 4); if (cubeWatcher != null) { int localX = blockToLocal(x); @@ -596,9 +596,11 @@ public void removePlayer(EntityPlayerMP player) { toSendUnload.add(columnWatcher); }); - toSendUnload.stream() - .filter(watcher -> watcher.containsPlayer(player)) - .forEach(watcher -> watcher.removePlayer(player)); + for (ColumnWatcher watcher : toSendUnload) { + if (watcher.containsPlayer(player)) { + watcher.removePlayer(player); + } + } this.players.remove(player.getEntityId()); // vanillaNetworkHandler.removePlayer(player); } @@ -884,6 +886,11 @@ public void removeSchedulesSendCubeToPlayer(Cube cube, EntityPlayerMP player) { cubesToSend.remove(player, cube); } + @Nullable + public CubeWatcher getCubeWatcher(int x, int y, int z) { + return this.cubeWatchers.get(x, y, z); + } + @Nullable public CubeWatcher getCubeWatcher(CubePos pos) { return this.cubeWatchers.get(pos.getX(), pos.getY(), pos.getZ()); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/SpawnCubes.java b/src/main/java/com/cardinalstar/cubicchunks/server/SpawnCubes.java index 008fb9f9..30f62ee6 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/SpawnCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/SpawnCubes.java @@ -124,7 +124,7 @@ private void addTickets(World world) { spawnCubeZ, r, ry, - (x, y, z) -> { serverCubeCache.loadCubeEagerly(x, y, z, ICubeProviderServer.Requirement.NBT); }); + (x, y, z) -> { serverCubeCache.loadCubeEagerly(x, y, z, ICubeProviderServer.Requirement.LIGHT); }); forEachCube(spawnCubeX, spawnCubeY, spawnCubeZ, r, ry, (cubeX, cubeY, cubeZ) -> { ICubeProviderServer.Requirement req; diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java new file mode 100644 index 00000000..d8b84649 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -0,0 +1,302 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.TimeUnit; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.storage.IThreadedFileIO; +import net.minecraft.world.storage.ThreadedFileIOBase; + +import net.minecraftforge.common.MinecraftForge; + +import com.cardinalstar.cubicchunks.CubicChunks; +import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; +import com.cardinalstar.cubicchunks.async.TaskPool; +import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.event.events.CubeEvent; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import it.unimi.dsi.fastutil.Pair; + +public class CubeIO implements ICubeIO, IThreadedFileIO { + + private final ICubicStorage storage; + private final IPreloadFailureDelegate preloadFailures; + + private final LinkedTransferQueue> columnQueue = new LinkedTransferQueue<>(); + private final LinkedTransferQueue> cubeQueue = new LinkedTransferQueue<>(); + + private final Map pendingColumns = new ConcurrentHashMap<>(); + private final Map pendingCubes = new ConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") + private final Cache columnCache = ((CacheBuilder) (Object) CacheBuilder.newBuilder()) + .expireAfterAccess(60, TimeUnit.SECONDS) + .softValues() + .initialCapacity(512) + .maximumSize(4096) + .build(); + + @SuppressWarnings("unchecked") + private final Cache cubeCache = ((CacheBuilder) (Object) CacheBuilder.newBuilder()) + .expireAfterAccess(60, TimeUnit.SECONDS) + .softValues() + .initialCapacity(1024) + .maximumSize(8192) + .build(); + + public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { + this.storage = storage; + this.preloadFailures = preloadFailures; + } + + @Override + public boolean columnExists(ChunkCoordIntPair pos) { + if (pendingColumns.containsKey(pos)) return true; + if (columnCache.asMap().containsKey(pos)) return true; + + try { + if (storage.columnExists(pos)) return true; + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not check if column {} exists", pos, e); + } + + return false; + } + + @Override + public boolean cubeExists(CubePos pos) { + if (pendingCubes.containsKey(pos)) return true; + if (cubeCache.asMap().containsKey(pos)) return true; + + try { + if (storage.cubeExists(pos)) return true; + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not check if cube {} exists", pos, e); + } + + return false; + } + + @Override + public NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureException { + NBTTagCompound tag = pendingColumns.get(pos); + + if (tag != null) return (NBTTagCompound) tag.copy(); + + tag = columnCache.getIfPresent(pos); + + if (tag != null) return (NBTTagCompound) tag.copy(); + + try { + tag = storage.readColumn(pos); + + if (tag == null) return null; + + columnCache.put(pos, (NBTTagCompound) tag.copy()); + + return tag; + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not read column {}", pos, e); + throw new LoadFailureException("Could not read column", e); + } + } + + @Override + public NBTTagCompound loadCube(CubePos pos) throws LoadFailureException { + NBTTagCompound tag = pendingCubes.get(pos); + + if (tag != null) return (NBTTagCompound) tag.copy(); + + tag = cubeCache.getIfPresent(pos); + + if (tag != null) return (NBTTagCompound) tag.copy(); + + try { + tag = storage.readCube(pos); + + if (tag == null) return null; + + cubeCache.put(pos, (NBTTagCompound) tag.copy()); + + return tag; + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not read cube {}", pos, e); + throw new LoadFailureException("Could not read cube", e); + } + } + + public void saveColumn(ChunkCoordIntPair pos, Chunk column) { + // NOTE: this function blocks the world thread + // make it as fast as possible by offloading processing to the IO thread + // except we have to write the NBT in this thread to avoid problems + // with concurrent access to world data structures + + // add the column to the save queue + NBTTagCompound tag = IONbtWriter.write(column); + + this.pendingColumns.put(pos, tag); + this.columnQueue.add(Pair.of(pos, tag)); + + column.isModified = false; + + ThreadedFileIOBase.threadedIOInstance.queueIO(this); + } + + public void saveCube(CubePos pos, Cube cube) { + cube.markSaved(); + + NBTTagCompound tag = IONbtWriter.write(cube); + + CubeEvent.SaveNBT event = new CubeEvent.SaveNBT(cube.getWorld(), cube.getCoords(), tag); + + MinecraftForge.EVENT_BUS.post(event); + + tag = event.tag; + + this.pendingCubes.put(pos, tag); + this.cubeQueue.add(Pair.of(pos, tag)); + + ThreadedFileIOBase.threadedIOInstance.queueIO(this); + } + + // only used by "/save-all flush" command + @Override + public void flush() throws IOException { + try { + this.drainQueueBlocking(); + + this.storage.flush(); + } catch (InterruptedException e) { + CubicChunks.LOGGER.catching(e); + } + } + + @Override + public void close() throws IOException { + try { + this.drainQueueBlocking(); + + this.storage.close(); + } catch (InterruptedException e) { + CubicChunks.LOGGER.catching(e); + } + } + + protected void drainQueueBlocking() throws InterruptedException { + // This has to submit itself to the I/O thread again, and also run in a loop, in order to avoid a potential race + // condition caused by the fact that ThreadedFileIOBase is incredibly stupid. + // Don't you think that if you're going to make an ASYNCHRONOUS executor, that you'd ensure that the code is + // ACTUALLY thread-safe? well, if you're mojang, apparently you don't. + + do { + ThreadedFileIOBase.threadedIOInstance.queueIO(this); + + ThreadedFileIOBase.threadedIOInstance.waitForFinish(); + } while (!this.pendingColumns.isEmpty() || !this.pendingCubes.isEmpty()); + } + + @Override + public boolean writeNextIO() { + try { + ArrayList> columns = new ArrayList<>(); + ArrayList> cubes = new ArrayList<>(); + + // Consume all dirty cubes and columns + this.columnQueue.drainTo(columns); + this.cubeQueue.drainTo(cubes); + + Map columnMap = new ConcurrentHashMap<>(); + Map cubeMap = new ConcurrentHashMap<>(); + + // Put them back into a map + columns.forEach(p -> columnMap.put(p.left(), p.right())); + cubes.forEach(p -> cubeMap.put(p.left(), p.right())); + + // Forward all tasks to the storage at once + this.storage.writeBatch( + new ICubicStorage.NBTBatch( + Collections.unmodifiableMap(columnMap), + Collections.unmodifiableMap(cubeMap))); + + // Remove from queue using remove(key, value) in order to avoid removing entries which have been modified + // since each request was queued. + columnMap.forEach(this.pendingColumns::remove); + cubeMap.forEach(this.pendingCubes::remove); + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not save chunks", e); + } + + // false = ok? + return false; + } + + @Override + public void preloadColumn(ChunkCoordIntPair pos) { + TaskPool.submit(new LoadColumnTask(storage, pos), tag -> { + if (tag == null) { + if (preloadFailures != null) preloadFailures.onColumnPreloadFailed(pos); + } else { + columnCache.put(pos, tag); + } + }); + } + + @Override + public void preloadCube(CubePos pos, CubeInitLevel wanted) { + TaskPool.submit(new LoadCubeTask(storage, pos), tag -> { + CubeInitLevel actual = tag == null ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag); + + if (tag == null || actual.ordinal() < wanted.ordinal()) { + if (preloadFailures != null) { + preloadFailures.onCubePreloadFailed(pos, actual, wanted); + } + } + + if (tag != null) { + cubeCache.put(pos, tag); + } + }); + } + + private static class LoadColumnTask implements ITask { + + private final ICubicStorage storage; + private final ChunkCoordIntPair pos; + + public LoadColumnTask(ICubicStorage storage, ChunkCoordIntPair pos) { + this.storage = storage; + this.pos = pos; + } + + @Override + public NBTTagCompound call() throws Exception { + return storage.readColumn(pos); + } + } + + private static class LoadCubeTask implements ITask { + + private final ICubicStorage storage; + private final CubePos pos; + + public LoadCubeTask(ICubicStorage storage, CubePos pos) { + this.storage = storage; + this.pos = pos; + } + + @Override + public NBTTagCompound call() throws Exception { + return storage.readCube(pos); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeInitLevel.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeInitLevel.java new file mode 100644 index 00000000..9b68e1a7 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeInitLevel.java @@ -0,0 +1,32 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; + +public enum CubeInitLevel { + + /** + * The cube has been created, but not generated. + */ + None, + /** + * The cube has been generated (terrain gen). Corresponds to {@link Requirement#GENERATE}. + */ + Generated, + /** + * The cube has been populated with structures. Corresponds to {@link Requirement#POPULATE}. + */ + Populated, + /** + * The cube's lighting has been calculated. Corresponds to {@link Requirement#LIGHT}. + */ + Lit; + + public static CubeInitLevel fromRequirement(Requirement effort) { + return switch (effort) { + case GET_CACHED, NBT, LOAD -> None; + case GENERATE -> Generated; + case POPULATE -> Populated; + case LIGHT -> Lit; + }; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java index 9fc5d0bc..ea0a7c16 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java @@ -27,7 +27,7 @@ default void onCubeLoaded(Cube cube) { * This is called when a cube is generated. It is called when a cube is loaded, then generated further, and when the * cube is initially generated from nothing. */ - default void onCubeGenerated(Cube cube, CubeLoaderServer.CubeInitLevel newLevel) { + default void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index 6e6dfbd7..578c34fb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -1,23 +1,15 @@ package com.cardinalstar.cubicchunks.server.chunkio; +import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; + import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedTransferQueue; -import java.util.function.BiConsumer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.storage.IThreadedFileIO; -import net.minecraft.world.storage.ThreadedFileIOBase; import net.minecraftforge.common.ForgeChunkManager; import com.cardinalstar.cubicchunks.CubicChunks; @@ -26,65 +18,69 @@ import com.cardinalstar.cubicchunks.api.XYZMap; import com.cardinalstar.cubicchunks.api.XZMap; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; -import com.cardinalstar.cubicchunks.api.worldgen.CubeGeneratorsRegistry; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; -import com.cardinalstar.cubicchunks.api.worldgen.LoadingData; +import com.cardinalstar.cubicchunks.api.worldgen.GenerationResult; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; +import com.cardinalstar.cubicchunks.event.events.ColumnEvent; +import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.util.Array3D; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; +import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; - -import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -public class CubeLoaderServer implements IThreadedFileIO, ICubeLoader { +public class CubeLoaderServer implements ICubeLoader { private final WorldServer world; - private final ICubicStorage storage; - private final ICubeGenerator generator; + private final ICubeIO cubeIO; + private final IWorldGenerator generator; private final CubeLoaderCallback callback; private final XYZMap cubes = new XYZMap<>(); private final XZMap columns = new XZMap<>(); - private final LinkedTransferQueue> columnQueue = new LinkedTransferQueue<>(); - private final LinkedTransferQueue> cubeQueue = new LinkedTransferQueue<>(); - - private final Map pendingColumns = new ConcurrentHashMap<>(); - private final Map pendingCubes = new ConcurrentHashMap<>(); - - private boolean pauseLoadCalls = false; + private int pauseLoadCalls; private final List pendingCubeLoads = new ArrayList<>(); private final List pendingColumnLoads = new ArrayList<>(); - public CubeLoaderServer(WorldServer world, ICubicStorage storage, ICubeGenerator generator, + private Array3D cache; + + public CubeLoaderServer(WorldServer world, ICubicStorage storage, IWorldGenerator generator, CubeLoaderCallback callback) { this.world = world; - this.storage = storage; + this.cubeIO = new CubeIO(storage, generator instanceof IPreloadFailureDelegate delegate ? delegate : null); this.generator = generator; this.callback = callback; } + @Override public void pauseLoadCalls() { - pauseLoadCalls = true; + pauseLoadCalls++; } + @Override public void unpauseLoadCalls() { - pauseLoadCalls = false; - - for (Chunk column : pendingColumnLoads) { - callback.onColumnLoaded(column); + if (pauseLoadCalls <= 0) { + pauseLoadCalls = 0; + return; } - pendingColumnLoads.clear(); + if (--pauseLoadCalls == 0) { + for (Chunk column : pendingColumnLoads) { + callback.onColumnLoaded(column); + } - for (Cube cube : pendingCubeLoads) { - callback.onCubeLoaded(cube); - } + pendingColumnLoads.clear(); - pendingCubeLoads.clear(); + for (Cube cube : pendingCubeLoads) { + callback.onCubeLoaded(cube); + } + + pendingCubeLoads.clear(); + } } @Override @@ -128,10 +124,26 @@ private ColumnInfo getColumnInfo(int x, int z, Requirement effort) { return success ? column : null; } + private Cube lastCube; + @Override public Cube getLoadedCube(int x, int y, int z) { + if (cache != null) { + Cube cube = cache.get(x, y, z); + + if (cube != null) return cube; + } else { + if (lastCube != null && lastCube.getX() == x && lastCube.getY() == y && lastCube.getZ() == z) { + return lastCube; + } + } + CubeInfo cube = cubes.get(x, y, z); + if (cache == null) { + lastCube = cube == null ? null : cube.cube; + } + return cube == null ? null : cube.cube; } @@ -139,17 +151,21 @@ public Cube getLoadedCube(int x, int y, int z) { public boolean cubeExists(int x, int y, int z) { if (getLoadedCube(x, y, z) != null) return true; - try { - if (storage.cubeExists(new CubePos(x, y, z))) return true; - } catch (IOException e) { - CubicChunks.LOGGER.error("Could not check if cube exists", e); - } + if (cubeIO.cubeExists(new CubePos(x, y, z))) return true; return false; } @Override public Cube getCube(int x, int y, int z, Requirement effort) { + if (cache != null) { + Cube cube = cache.get(x, y, z); + + if (cube != null && cube.getInitLevel().ordinal() >= CubeInitLevel.fromRequirement(effort).ordinal()) { + return cube; + } + } + CubeInfo cubeInfo = cubes.get(x, y, z); Cube loaded = cubeInfo != null ? cubeInfo.cube : null; @@ -163,7 +179,7 @@ public Cube getCube(int x, int y, int z, Requirement effort) { cubes.put(cubeInfo = new CubeInfo(x, y, z)); } - CubeInitLevel before = cubeInfo.getInitLevel(); + boolean changed = cubeInfo.updateInitLevel(); boolean success; @@ -173,19 +189,60 @@ public Cube getCube(int x, int y, int z, Requirement effort) { throw new RuntimeException(String.format("Could not generate cube at %d,%d,%d", x, y, z), throwable); } - CubeInitLevel after = cubeInfo.getInitLevel(); + changed |= cubeInfo.updateInitLevel(); if (cubeInfo.cube == null) { cubes.remove(cubeInfo); } else { - if (success && before != after) { - callback.onCubeGenerated(cubeInfo.cube, after); + if (success && changed) { + callback.onCubeGenerated(cubeInfo.cube, cubeInfo.getInitLevel()); } } + if (success && cache != null) { + cache.set(x, y, z, cubeInfo.cube); + } + return success ? cubeInfo.cube : null; } + @Override + public void onCubeGenerated(int x, int y, int z) { + CubeInfo cubeInfo = cubes.get(x, y, z); + + if (cubeInfo == null) return; + + if (cubeInfo.updateInitLevel()) { + callback.onCubeGenerated(cubeInfo.cube, cubeInfo.getInitLevel()); + } + } + + @Override + public void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz, Requirement effort) { + cache = new Array3D<>(spanx, spany, spanz, x, y, z, new Cube[spanx * spany * spanz]); + + for (int x2 = 0; x2 < spanx; x2++) { + for (int y2 = 0; y2 < spany; y2++) { + for (int z2 = 0; z2 < spanz; z2++) { + cache.set(x + x2, y + y2, z + z2, getCube(x + x2, y + y2, z + z2, effort)); + } + } + } + } + + @Override + public void uncacheCubes() { + cache = null; + } + + public void preloadColumn(ChunkCoordIntPair pos) { + cubeIO.preloadColumn(pos); + } + + public void preloadCube(CubePos pos, CubeInitLevel level) { + cubeIO.preloadCube(pos, level); + } + @Override public void unloadCube(int x, int y, int z) { CubeInfo info = cubes.remove(x, y, z); @@ -207,19 +264,18 @@ public void save(boolean saveAll) { processedLighting = true; } - saveCube(cube); + cubeIO.saveCube(cube.pos, cube.cube); } } for (ColumnInfo column : columns) { if (column.column != null && column.column.needsSaving(saveAll)) { - saveColumn(column); + cubeIO.saveColumn(column.pos, column.column); } } - - ThreadedFileIOBase.threadedIOInstance.queueIO(this); } + @Override public void saveColumn(Chunk column) { if (column == null) return; @@ -235,27 +291,10 @@ public void saveColumn(Chunk column) { return; } - saveColumn(columnInfo); - } - - private void saveColumn(ColumnInfo column) { - // NOTE: this function blocks the world thread - // make it as fast as possible by offloading processing to the IO thread - // except we have to write the NBT in this thread to avoid problems - // with concurrent access to world data structures - - // add the column to the save queue - NBTTagCompound tag = IONbtWriter.write(column.column); - - this.pendingColumns.put(column.pos, tag); - this.columnQueue.add(Pair.of(column.pos, tag)); - - column.column.isModified = false; - - // signal the IO thread to process the save queue - ThreadedFileIOBase.threadedIOInstance.queueIO(this); + cubeIO.saveColumn(columnInfo.pos, column); } + @Override public void saveCube(Cube cube) { if (cube == null || cube instanceof BlankCube) return; @@ -271,22 +310,10 @@ public void saveCube(Cube cube) { return; } - saveCube(cubeInfo); - } - - private void saveCube(CubeInfo cube) { - if (cube.cube == null) return; - - // NOTE: this function blocks the world thread, so make it fast - - NBTTagCompound tag = IONbtWriter.write(cube.cube); - - cube.cube.markSaved(); - - this.pendingCubes.put(cube.pos, tag); - this.cubeQueue.add(Pair.of(cube.pos, tag)); + cubeIO.saveCube(cubeInfo.pos, cube); } + @Override public void doGC() { var persistentChunks = ForgeChunkManager.getPersistentChunksFor(world); @@ -350,75 +377,53 @@ public void doGC() { } } - // only used by "/save-all flush" command @Override public void flush() throws IOException { - try { - this.drainQueueBlocking(); - - this.storage.flush(); - } catch (InterruptedException e) { - CubicChunks.LOGGER.catching(e); - } + cubeIO.flush(); } @Override public void close() throws IOException { - try { - this.drainQueueBlocking(); - - this.storage.close(); - } catch (InterruptedException e) { - CubicChunks.LOGGER.catching(e); - } + cubeIO.close(); } - protected void drainQueueBlocking() throws InterruptedException { - // This has to submit itself to the I/O thread again, and also run in a loop, in order to avoid a potential race - // condition caused by the fact that ThreadedFileIOBase is incredibly stupid. - // Don't you think that if you're going to make an ASYNCHRONOUS executor, that you'd ensure that the code is - // ACTUALLY thread-safe? well, if you're mojang, apparently you don't. + private void handleSideEffects(GenerationResult result) { + for (Chunk column : result.columnSideEffects) { + ColumnInfo info = columns.get(column.xPosition, column.zPosition); - do { - ThreadedFileIOBase.threadedIOInstance.queueIO(this); - - ThreadedFileIOBase.threadedIOInstance.waitForFinish(); - } while (!this.pendingColumns.isEmpty() || !this.pendingCubes.isEmpty()); - } + if (info == null) { + info = new ColumnInfo(column.xPosition, column.zPosition); + columns.put(info); + } - @Override - public boolean writeNextIO() { - try { - ArrayList> columns = new ArrayList<>(); - ArrayList> cubes = new ArrayList<>(); + info.source = ObjectSource.GeneratedSideEffect; + info.column = column; - // Consume all dirty cubes and columns - this.columnQueue.drainTo(columns); - this.cubeQueue.drainTo(cubes); + info.onColumnLoaded(); + } - Map columnMap = new ConcurrentHashMap<>(); - Map cubeMap = new ConcurrentHashMap<>(); + for (Cube cube : result.cubeSideEffects) { + CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); - // Put them back into a map - columns.forEach(p -> columnMap.put(p.left(), p.right())); - cubes.forEach(p -> cubeMap.put(p.left(), p.right())); + if (info == null) { + info = new CubeInfo(cube.getX(), cube.getY(), cube.getZ()); + info.column = columns.get(cube.getX(), cube.getZ()); + cubes.put(info); + } - // Forward all tasks to the storage at once - this.storage.writeBatch( - new ICubicStorage.NBTBatch( - Collections.unmodifiableMap(columnMap), - Collections.unmodifiableMap(cubeMap))); + info.source = ObjectSource.GeneratedSideEffect; + info.cube = cube; - // Remove from queue using remove(key, value) in order to avoid removing entries which have been modified - // since each request was queued. - columnMap.forEach(this.pendingColumns::remove); - cubeMap.forEach(this.pendingCubes::remove); - } catch (IOException e) { - CubicChunks.LOGGER.error("Could not save chunks", e); + info.onCubeLoaded(); + callback.onCubeGenerated(cube, cube.getInitLevel()); } + } - // false = ok? - return false; + private enum ObjectSource { + None, + Disk, + Generated, + GeneratedSideEffect } private class ColumnInfo implements XZAddressable { @@ -427,6 +432,7 @@ private class ColumnInfo implements XZAddressable { public NBTTagCompound tag; public Chunk column; + public ObjectSource source; public final ObjectOpenHashSet containedCubes = new ObjectOpenHashSet<>(); @@ -455,43 +461,36 @@ public boolean initialize(Requirement effort) throws IOException { if (column != null) return true; if (effort == Requirement.LOAD) return false; - Optional generated = generator - .tryGenerateColumn(world, pos.chunkXPos, pos.chunkZPos, null, null, true); + GenerationResult result = generator.provideColumn(world, pos.chunkXPos, pos.chunkZPos); - if (!generated.isPresent()) return false; + if (result == null) return false; - column = generated.get(); + this.column = result.object; + source = ObjectSource.Generated; onColumnLoaded(); - saveColumn(this); + cubeIO.saveColumn(pos, column); + + handleSideEffects(result); return true; } - private boolean loadNBT() throws IOException { + private boolean loadNBT() { if (tag != null) return true; - tag = pendingColumns.get(pos); - - if (tag == null) { - tag = CubeLoaderServer.this.storage.readColumn(pos); - } + tag = cubeIO.loadColumn(pos); if (tag == null) return false; - Collection>> asyncCallbacks = CubeGeneratorsRegistry - .getColumnAsyncLoadingCallbacks(); + source = ObjectSource.Disk; - if (!asyncCallbacks.isEmpty()) { - LoadingData chunkLoadingData = new LoadingData<>(pos, tag); + ColumnEvent.LoadNBT event = new ColumnEvent.LoadNBT(world, pos, tag); - for (BiConsumer> cons : asyncCallbacks) { - cons.accept(world, chunkLoadingData); - } + EVENT_BUS.post(event); - tag = chunkLoadingData.getNbt(); - } + tag = event.tag; return true; } @@ -509,9 +508,11 @@ private boolean loadColumn() { } public void onColumnLoaded() { + ((IColumnInternal) column).setColumn(true); + column.onChunkLoad(); - if (!pauseLoadCalls) { + if (pauseLoadCalls == 0) { callback.onColumnLoaded(column); } else { pendingColumnLoads.add(column); @@ -520,7 +521,7 @@ public void onColumnLoaded() { public void onColumnUnloaded() { if (column.isModified) { - saveColumn(this); + cubeIO.saveColumn(pos, column); } column.onChunkUnload(); @@ -537,15 +538,11 @@ private class CubeInfo implements XYZAddressable { public ColumnInfo column; - private CubeSource source = CubeSource.None; + private ObjectSource source = ObjectSource.None; public boolean generating = false; - enum CubeSource { - None, - Disk, - Generated - } + private CubeInitLevel lastKnownLevel = CubeInitLevel.None; public CubeInfo(int x, int y, int z) { this.pos = new CubePos(x, y, z); @@ -584,7 +581,7 @@ public boolean initialize(Requirement effort) throws IOException { } // If we loaded the NBT from disk successfully and we don't already have a cube loaded, try to load it - if (tag != null && source == CubeSource.None) { + if (tag != null && source == ObjectSource.None) { loadCube(); if (effort == Requirement.LOAD) return cube != null; @@ -597,33 +594,18 @@ public boolean initialize(Requirement effort) throws IOException { return generate(requestedInitLevel); } - private boolean loadNBT() throws IOException { - - tag = pendingCubes.get(pos); + private boolean loadNBT() { + if (tag != null) return true; - if (tag == null) { - tag = CubeLoaderServer.this.storage.readCube(pos); - } + tag = cubeIO.loadCube(pos); if (tag == null) return false; - Collection>> asyncCallbacks = CubeGeneratorsRegistry - .getCubeAsyncLoadingCallbacks(); + CubeEvent.LoadNBT event = new CubeEvent.LoadNBT(world, pos, tag); - if (!asyncCallbacks.isEmpty()) { - LoadingData chunkLoadingData = new LoadingData<>(pos, tag); + EVENT_BUS.post(event); - for (BiConsumer> cons : asyncCallbacks) { - cons.accept(world, chunkLoadingData); - } - - this.tag = chunkLoadingData.getNbt(); - } - - // TODO PROBABLY DON'T NEED TO DO THIS. WORLDS DON'T CHANGE VERSIONS. - // if (nbt != null) { //fix column data - // nbt = FMLCommonHandler.instance().getDataFixer().process(FixTypes.CHUNK, nbt); - // } + tag = event.tag; return true; } @@ -633,7 +615,7 @@ private void loadCube() throws IOException { this.cube = IONbtReader.readCube(column.column, getX(), getY(), getZ(), tag); - source = CubeSource.Disk; + source = ObjectSource.Disk; onCubeLoaded(); @@ -657,58 +639,60 @@ private boolean generate(CubeInitLevel requestedInitLevel) { if (isInitedTo(requestedInitLevel)) return true; // If this cube hasn't been generated at all (i.e. it was never on the disk), generate it - if (source == CubeSource.None) { + if (source == ObjectSource.None) { ensureColumn(); + // Column had a side effect that initialized this CubeInfo + if (isInitedTo(requestedInitLevel)) return true; + if (generating) { throw new IllegalStateException( "Cannot recursively generate a cube that is already being generated"); } - Optional generated; - + GenerationResult result; try { - generating = true; + this.generating = true; - generated = generator.tryGenerateCube(column.column, pos.getX(), pos.getY(), pos.getZ(), true); + result = generator.provideCube(column.column, pos.getX(), pos.getY(), pos.getZ()); } finally { - generating = false; + this.generating = false; } - if (!generated.isPresent()) return false; + if (result == null) return false; - cube = generated.get(); - source = CubeSource.Generated; + this.cube = result.object; + source = ObjectSource.Generated; onCubeLoaded(); - saveCube(this); + cubeIO.saveCube(pos, cube); + + handleSideEffects(result); } - boolean generated = cube.getInitLevel() == CubeInitLevel.Generated; + boolean generated = isInitedTo(CubeInitLevel.Generated); // We were only asked to generate it and we did so successfully if (requestedInitLevel == CubeInitLevel.Generated) return generated; + if (!generated) return false; // If this cube hasn't been populated at all, generate the required cubes and populate this cube. - if (generated) { - generator.populate(cube); - } + generator.populate(cube); - boolean populated = cube.getInitLevel() == CubeInitLevel.Populated; + boolean populated = isInitedTo(CubeInitLevel.Populated); if (requestedInitLevel == CubeInitLevel.Populated) return populated; + if (!populated) return false; - if (populated) { - if (!cube.isInitialLightingDone() || !cube.isSurfaceTracked()) { - ((ICubicWorldInternal) world).getLightingManager() - .doFirstLight(cube); - cube.setInitialLightingDone(true); - } + if (!cube.isInitialLightingDone() || !cube.isSurfaceTracked()) { + ((ICubicWorldInternal) world).getLightingManager() + .doFirstLight(cube); + cube.setInitialLightingDone(true); + } - if (!cube.isSurfaceTracked()) { - cube.trackSurface(); - } + if (!cube.isSurfaceTracked()) { + cube.trackSurface(); } return cube.getInitLevel() == CubeInitLevel.Lit; @@ -717,21 +701,29 @@ private boolean generate(CubeInitLevel requestedInitLevel) { public void onCubeLoaded() { ensureColumn(); + updateInitLevel(); + ((IColumn) column.column).addCube(cube); column.containedCubes.add(this); cube.onCubeLoad(); - if (!pauseLoadCalls) { + if (pauseLoadCalls == 0) { callback.onCubeLoaded(cube); } else { pendingCubeLoads.add(cube); } } + public boolean updateInitLevel() { + CubeInitLevel prev = lastKnownLevel; + lastKnownLevel = getInitLevel(); + return prev != lastKnownLevel; + } + public void onCubeUnloaded() { if (this.isInitedTo(CubeInitLevel.Generated)) { - saveCube(this); + cubeIO.saveCube(pos, cube); } ((IColumn) column.column).removeCube(getY()); @@ -751,42 +743,4 @@ private void ensureColumn() { } } } - - public enum CubeSource { - Unknown, - Generated, - Loaded - } - - public enum CubeInitLevel { - - /** - * The cube has been created, but not generated. - */ - None, - /** - * The cube has been generated (terrain gen). - * Corresponds to {@link Requirement#GENERATE}. - */ - Generated, - /** - * The cube has been populated with structures. - * Corresponds to {@link Requirement#POPULATE}. - */ - Populated, - /** - * The cube's lighting has been calculated. - * Corresponds to {@link Requirement#LIGHT}. - */ - Lit; - - public static CubeInitLevel fromRequirement(Requirement effort) { - return switch (effort) { - case GET_CACHED, NBT, LOAD -> None; - case GENERATE -> Generated; - case POPULATE -> Populated; - case LIGHT -> Lit; - }; - } - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java new file mode 100644 index 00000000..9a85290a --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java @@ -0,0 +1,26 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import java.io.Closeable; +import java.io.Flushable; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.chunk.Chunk; + +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; + +public interface ICubeIO extends Flushable, Closeable { + + boolean columnExists(ChunkCoordIntPair pos); + boolean cubeExists(CubePos pos); + + void saveColumn(ChunkCoordIntPair pos, Chunk column); + void saveCube(CubePos pos, Cube cube); + + NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureException; + NBTTagCompound loadCube(CubePos pos) throws LoadFailureException; + + void preloadColumn(ChunkCoordIntPair pos); + void preloadCube(CubePos pos, CubeInitLevel level); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java index 0b5b845a..e0700fc2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java @@ -2,11 +2,11 @@ import java.io.Closeable; import java.io.Flushable; -import java.io.IOException; import net.minecraft.world.chunk.Chunk; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; +import com.cardinalstar.cubicchunks.api.util.Box; +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; import com.cardinalstar.cubicchunks.world.cube.Cube; public interface ICubeLoader extends Flushable, Closeable { @@ -15,17 +15,29 @@ public interface ICubeLoader extends Flushable, Closeable { void unpauseLoadCalls(); - Chunk getColumn(int x, int z, ICubeProviderServer.Requirement effort); + Chunk getColumn(int x, int z, Requirement effort); default boolean columnExists(int x, int z) { - return getColumn(x, z, ICubeProviderServer.Requirement.GET_CACHED) != null; + return getColumn(x, z, Requirement.GET_CACHED) != null; } Cube getLoadedCube(int x, int y, int z); boolean cubeExists(int x, int y, int z); - Cube getCube(int x, int y, int z, ICubeProviderServer.Requirement effort); + + Cube getCube(int x, int y, int z, Requirement effort); + /// Notifies this loader that a cube was generated further, either by a populator or something else. + void onCubeGenerated(int x, int y, int z); + + void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz, Requirement effort); + default void cacheCubes(Box box, Requirement effort) { + cacheCubes( + box.getX1(), box.getY1(), box.getZ1(), + box.getX2() - box.getX1(), box.getY2() - box.getY1(), box.getZ2() - box.getZ1(), + effort); + } + void uncacheCubes(); void unloadCube(int x, int y, int z); @@ -36,11 +48,4 @@ default boolean columnExists(int x, int z) { void saveCube(Cube cube); void doGC(); - - // only used by "/save-all flush" command - @Override - void flush() throws IOException; - - @Override - void close() throws IOException; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java index a607a2ac..21d9f875 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java @@ -46,6 +46,7 @@ import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; @@ -63,6 +64,7 @@ static Chunk readColumn(World world, int x, int z, NBTTagCompound nbt) { readOpacityIndex(level, column); column.isModified = false; // its exactly the same as on disk so its not modified + ((IColumnInternal)column).setColumn(true); return column; // TODO: use Chunk, not IColumn, whenever possible } @@ -108,6 +110,20 @@ private static void readOpacityIndex(NBTTagCompound nbt, Chunk chunk) {// biomes ((ServerHeightMap) hmap).readData(nbt.getByteArray("OpacityIndex")); } + static CubeInitLevel getCubeInitLevel(NBTTagCompound nbt) { + NBTTagCompound level = nbt.getCompoundTag("Level"); + + boolean isSurfaceTracked = level.getBoolean("isSurfaceTracked"); + short population = level.getShort("population"); + boolean initLightDone = level.getBoolean("initLightDone"); + + if (population != Cube.POP_ALL) return CubeInitLevel.Generated; + + if (!isSurfaceTracked && !initLightDone) return CubeInitLevel.Populated; + + return CubeInitLevel.Lit; + } + static Cube readCube(Chunk column, final int cubeX, final int cubeY, final int cubeZ, NBTTagCompound nbt) throws IOException { if (column.xPosition != cubeX || column.zPosition != cubeZ) { @@ -152,11 +168,10 @@ static Cube readCube(Chunk column, final int cubeX, final int cubeY, final int c final Cube cube = new Cube(column, cubeY); // set the worldgen stage - cube.setPopulated(level.getBoolean("populated")); + cube.setPopulationStatus(level.getShort("population")); // previous versions will get their surface tracking redone. This is intended cube.setSurfaceTracked(level.getBoolean("isSurfaceTracked")); - cube.setFullyPopulated(level.getBoolean("fullyPopulated")); // this status will get unset again in readLightingInfo() if the lighting engine is changed (LightingInfoType). cube.setInitialLightingDone(level.getBoolean("initLightDone")); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java index 8666a0df..ca46bb6c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java @@ -46,14 +46,12 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.IHeightMap; -import com.cardinalstar.cubicchunks.event.events.CubeDataEvent; import com.cardinalstar.cubicchunks.lighting.ILightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; - import cpw.mods.fml.common.FMLLog; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -93,7 +91,6 @@ static NBTTagCompound write(final Cube cube) { writeScheduledTicks(cube, level); writeLightingInfo(cube, level); writeBiomes(cube, level); - writeModData(cube, cubeNbt); return cubeNbt; } @@ -138,9 +135,8 @@ private static void writeBaseCube(Cube cube, NBTTagCompound cubeNbt) { cubeNbt.setInteger("z", cube.getZ()); // save the worldgen stage and the target stage - cubeNbt.setBoolean("populated", cube.isPopulated()); + cubeNbt.setShort("population", cube.getPopulationStatus()); cubeNbt.setBoolean("isSurfaceTracked", cube.isSurfaceTracked()); - cubeNbt.setBoolean("fullyPopulated", cube.isFullyPopulated()); cubeNbt.setBoolean("initLightDone", cube.isInitialLightingDone()); @@ -261,10 +257,6 @@ private static void writeLightingInfo(Cube cube, NBTTagCompound cubeNbt) { lightingManager.writeToNbt(cube, lightingInfo); } - private static void writeModData(Cube cube, NBTTagCompound level) { - EVENT_BUS.post(new CubeDataEvent.Save(cube, level)); - } - private static void writeBiomes(Cube cube, NBTTagCompound nbt) {// biomes byte[] biomes = cube.getBiomeArray(); if (biomes != null) nbt.setByteArray("Biomes3D", biomes); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java new file mode 100644 index 00000000..0e3b675c --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java @@ -0,0 +1,14 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import net.minecraft.world.ChunkCoordIntPair; + +import com.cardinalstar.cubicchunks.util.CubePos; + +/// Tells the world generator to start preloading the noise arrays, etc for a cube or column. +/// Note that these methods are called on a background worker thread and need to be thread-safe. +public interface IPreloadFailureDelegate { + + void onColumnPreloadFailed(ChunkCoordIntPair pos); + void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel wanted); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/LoadFailureException.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/LoadFailureException.java new file mode 100644 index 00000000..05bbab4b --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/LoadFailureException.java @@ -0,0 +1,21 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +public class LoadFailureException extends RuntimeException { + + public LoadFailureException(String message) { + super(message); + } + + public LoadFailureException(String message, Throwable cause) { + super(message, cause); + } + + public LoadFailureException(Throwable cause) { + super(cause); + } + + public LoadFailureException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java index 8406d9c0..c0f2e8e3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java @@ -38,12 +38,10 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.ChunkCoordIntPair; -import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; import com.cardinalstar.cubicchunks.server.chunkio.region.ShadowPagingRegion; import com.cardinalstar.cubicchunks.util.CubePos; - import cubicchunks.regionlib.impl.EntryLocation2D; import cubicchunks.regionlib.impl.EntryLocation3D; import cubicchunks.regionlib.impl.SaveCubeColumns; @@ -84,20 +82,13 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName())))), + (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName())))), new SharedCachedRegionProvider<>( new SimpleRegionProvider<>( new EntryLocation2D.Provider(), part2d, - (keyProvider, - regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName() + ".ext"))))); + (keyProvider, regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), + (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName() + ".ext"))))); @SuppressWarnings("unchecked") SaveSection3D section3d = new SaveSection3D( new SharedCachedRegionProvider<>( @@ -110,20 +101,14 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName())))), + (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName())))), new SharedCachedRegionProvider<>( new SimpleRegionProvider<>( new EntryLocation3D.Provider(), part3d, (keyProvider, regionKey) -> new ExtRegion<>(part3d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName() + ".ext"))))); + (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName() + ".ext"))))); return new SaveCubeColumns(section2d, section3d); } else { @@ -235,7 +220,6 @@ public void writeBatch(NBTBatch batch) throws IOException { entry -> entry.getValue() .nioBuffer()))); } - CubicChunks.LOGGER.info("Saved batch: {} columns and {} cubes", batch.columns.size(), batch.cubes.size()); } finally { compressedColumns.values() .forEach(ByteBuf::release); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java index 188b0663..7a7b76f6 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java @@ -223,8 +223,7 @@ private boolean writeDataPass(Map entries, List tempBuffers = new ArrayList<>(); ByteBuffer lengthPrefixBuffer = ByteBuffer.allocate(Integer.BYTES); - for (Iterator> itr = entries.entrySet() - .iterator(); itr.hasNext();) { + for (Iterator> itr = entries.entrySet().iterator(); itr.hasNext();) { Map.Entry entry = itr.next(); K key = entry.getKey(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/Array3D.java b/src/main/java/com/cardinalstar/cubicchunks/util/Array3D.java new file mode 100644 index 00000000..deb64995 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Array3D.java @@ -0,0 +1,48 @@ +package com.cardinalstar.cubicchunks.util; + +public class Array3D { + + private final int spanx; + private final int spany; + private final int spanz; + private final int spanslice; + private final int offsetx; + private final int offsety; + private final int offsetz; + private final T[] data; + + public Array3D(int spanx, int spany, int spanz, int offsetx, int offsety, int offsetz, T[] data) { + this.spanx = spanx; + this.spany = spany; + this.spanz = spanz; + this.spanslice = spanx * spany; + this.offsetx = offsetx; + this.offsety = offsety; + this.offsetz = offsetz; + this.data = data; + } + + public final void set(final int x, final int y, final int z, final T value) { + final int relx = x - offsetx; + final int rely = y - offsety; + final int relz = z - offsetz; + + if (relx < 0 || relx >= spanx) return; + if (rely < 0 || rely >= spany) return; + if (relz < 0 || relz >= spanz) return; + + data[relx + (rely * spanx) + (relz * spanslice)] = value; + } + + public final T get(final int x, final int y, final int z) { + final int relx = x - offsetx; + final int rely = y - offsety; + final int relz = z - offsetz; + + if (relx < 0 || relx >= spanx) return null; + if (rely < 0 || rely >= spany) return null; + if (relz < 0 || relz >= spanz) return null; + + return data[relx + (rely * spanx) + (relz * spanslice)]; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosMap.java b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosMap.java new file mode 100644 index 00000000..f829a6cc --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosMap.java @@ -0,0 +1,201 @@ +package com.cardinalstar.cubicchunks.util; + +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.pack; +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.unpackX; +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.unpackY; +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.unpackZ; + +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.jetbrains.annotations.NotNull; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.AbstractObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; + +@SuppressWarnings("unused") +public class BlockPosMap extends Long2ObjectOpenHashMap { + + public V get(int blockX, int blockY, int blockZ) { + return super.get(pack(blockX, blockY, blockZ)); + } + + public V remove(int blockX, int blockY, int blockZ) { + return super.remove(pack(blockX, blockY, blockZ)); + } + + public boolean containsKey(int blockX, int blockY, int blockZ) { + return super.containsKey(pack(blockX, blockY, blockZ)); + } + + public V put(int blockX, int blockY, int blockZ, V v) { + return super.put(pack(blockX, blockY, blockZ), v); + } + + public interface BlockPosMapComputeFn { + + V apply(int blockX, int blockY, int blockZ); + } + + public V computeIfAbsent(int blockX, int blockY, int blockZ, @NotNull BlockPosMapComputeFn mappingFunction) { + V v; + + long key = pack(blockX, blockY, blockZ); + + if ((v = get(key)) == null) { + V newValue; + if ((newValue = mappingFunction.apply(blockX, blockY, blockZ)) != null) { + put(key, newValue); + return newValue; + } + } + + return v; + } + + public interface FastEntrySet extends ObjectSet> { + + /** + * Returns a fast iterator over this entry set; the iterator might return always the same entry + * instance, suitably mutated. + * + * @return a fast iterator over this entry set; the iterator might return always the same + * {@link java.util.Map.Entry} instance, suitably mutated. + */ + ObjectIterator> fastIterator(); + + /** + * Iterates quickly over this entry set; the iteration might happen always on the same entry + * instance, suitably mutated. + * + *

+ * + * This default implementation just delegates to {@link #forEach(Consumer)}. + * + * @param consumer a consumer that will by applied to the entries of this set; the entries might be + * represented by the same entry instance, suitably mutated. + * @since 8.1.0 + */ + default void fastForEach(final Consumer> consumer) { + forEach(consumer); + } + } + + private FastChunkEntrySet entrySet = new FastChunkEntrySet(); + + public FastEntrySet fastEntrySet() { + if (entrySet == null) entrySet = new FastChunkEntrySet(); + + return entrySet; + } + + public Iterable> fastEntryIterable() { + return () -> fastEntrySet().fastIterator(); + } + + public Stream> fastEntryStream() { + return StreamSupport.stream( + Spliterators.spliterator( + fastEntryIterable().iterator(), + size(), + Spliterator.SIZED | Spliterator.NONNULL | Spliterator.DISTINCT), + false); + } + + private class FastChunkEntrySet extends AbstractObjectSet> implements FastEntrySet { + + @Override + public ObjectIterator> fastIterator() { + BlockPosEntry entry = new BlockPosEntry<>(); + + var iter = BlockPosMap.this.long2ObjectEntrySet() + .fastIterator(); + + return new ObjectIterator<>() { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public BlockPosEntry next() { + var e = iter.next(); + + entry.setKeyImpl(e.getLongKey()); + entry.setValueImpl(e.getValue()); + + return entry; + } + }; + } + + @Override + public @NotNull ObjectIterator> iterator() { + var iter = BlockPosMap.this.long2ObjectEntrySet() + .fastIterator(); + + return new ObjectIterator<>() { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public BlockPosEntry next() { + var e = iter.next(); + + return new BlockPosEntry<>(e.getLongKey(), e.getValue()); + } + }; + } + + @Override + public int size() { + return BlockPosMap.this.size(); + } + } + + public static class BlockPosEntry extends BasicEntry { + + public BlockPosEntry() {} + + public BlockPosEntry(Long key, T value) { + super(key, value); + } + + public BlockPosEntry(long key, T value) { + super(key, value); + } + + public BlockPosEntry(int blockX, int blockY, int blockZ, T value) { + super(pack(blockX, blockY, blockZ), value); + } + + private void setKeyImpl(long key) { + super.key = key; + } + + private void setValueImpl(T value) { + super.value = value; + } + + public final int getBlockX() { + return unpackX(getLongKey()); + } + + public final int getBlockY() { + return unpackY(getLongKey()); + } + + public final int getBlockZ() { + return unpackZ(getLongKey()); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java new file mode 100644 index 00000000..d82ec8b4 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java @@ -0,0 +1,78 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.BitSet; +import java.util.Iterator; + +import org.joml.Vector3i; +import org.joml.Vector3ic; + +import com.google.common.collect.AbstractIterator; + +public class BooleanArray3D extends BitSet implements Iterable { + + private final int spanx, spany, spanz, spanslice; + + public BooleanArray3D(int spanx, int spany, int spanz) { + this.spanx = spanx; + this.spany = spany; + this.spanz = spanz; + this.spanslice = spanx * spany; + } + + public BooleanArray3D(int spanx, int spany, int spanz, boolean[] data) { + this.spanx = spanx; + this.spany = spany; + this.spanz = spanz; + this.spanslice = spanx * spany; + + for (int i = 0; i < data.length; i++) { + if (data[i]) { + set(i); + } + } + } + + public void set(int x, int y, int z) { + set(index(x, y, z)); + } + + public void clear(int x, int y, int z) { + clear(index(x, y, z)); + } + + public boolean get(int x, int y, int z) { + return get(index(x, y, z)); + } + + public Iterator iterator() { + return new AbstractIterator<>() { + + private boolean init = false; + private int index; + private final Vector3i v = new Vector3i(); + + @Override + protected Vector3ic computeNext() { + if (!init) { + init = true; + index = BooleanArray3D.this.nextSetBit(0); + } else { + index = BooleanArray3D.this.nextSetBit(index + 1); + } + + if (index == -1) { + this.endOfData(); + return null; + } + + v.set(index % spanx, (index / spanx) % spany, index / spanslice); + + return v; + } + }; + } + + private int index(int x, int y, int z) { + return x + (y * spanx) + (z * spanslice); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/ChunkMap.java b/src/main/java/com/cardinalstar/cubicchunks/util/ChunkMap.java new file mode 100644 index 00000000..c4d4b1fe --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/ChunkMap.java @@ -0,0 +1,209 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.jetbrains.annotations.NotNull; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.AbstractObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; + +@SuppressWarnings("unused") +public class ChunkMap extends Long2ObjectOpenHashMap { + + public V get(int chunkX, int chunkZ) { + return super.get(pack(chunkX, chunkZ)); + } + + public V remove(int chunkX, int chunkZ) { + return super.remove(pack(chunkX, chunkZ)); + } + + public boolean containsKey(int chunkX, int chunkZ) { + return super.containsKey(pack(chunkX, chunkZ)); + } + + public V put(int chunkX, int chunkZ, V v) { + return super.put(pack(chunkX, chunkZ), v); + } + + public interface ChunkMapComputeFn { + + V apply(int chunkX, int chunkZ); + } + + public V computeIfAbsent(int chunkX, int chunkZ, @NotNull ChunkMapComputeFn mappingFunction) { + V v; + + long key = pack(chunkX, chunkZ); + + if ((v = get(key)) == null) { + V newValue; + if ((newValue = mappingFunction.apply(chunkX, chunkZ)) != null) { + put(key, newValue); + return newValue; + } + } + + return v; + } + + public interface FastEntrySet extends ObjectSet> { + + /** + * Returns a fast iterator over this entry set; the iterator might return always the same entry + * instance, suitably mutated. + * + * @return a fast iterator over this entry set; the iterator might return always the same + * {@link java.util.Map.Entry} instance, suitably mutated. + */ + ObjectIterator> fastIterator(); + + /** + * Iterates quickly over this entry set; the iteration might happen always on the same entry + * instance, suitably mutated. + * + *

+ * + * This default implementation just delegates to {@link #forEach(Consumer)}. + * + * @param consumer a consumer that will by applied to the entries of this set; the entries might be + * represented by the same entry instance, suitably mutated. + * @since 8.1.0 + */ + default void fastForEach(final Consumer> consumer) { + forEach(consumer); + } + } + + private FastChunkEntrySet entrySet = new FastChunkEntrySet(); + + public FastEntrySet chunkEntrySet() { + if (entrySet == null) entrySet = new FastChunkEntrySet(); + + return entrySet; + } + + public Iterable> fastEntryIterable() { + return () -> chunkEntrySet().fastIterator(); + } + + public Stream> fastEntryStream() { + return StreamSupport.stream( + Spliterators.spliterator( + fastEntryIterable().iterator(), + size(), + Spliterator.SIZED | Spliterator.NONNULL | Spliterator.DISTINCT), + false); + } + + private class FastChunkEntrySet extends AbstractObjectSet> implements FastEntrySet { + + @Override + public ObjectIterator> fastIterator() { + ChunkEntry entry = new ChunkEntry<>(); + + var iter = ChunkMap.this.long2ObjectEntrySet() + .fastIterator(); + + return new ObjectIterator<>() { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public ChunkEntry next() { + var e = iter.next(); + + entry.setKey(e.getLongKey()); + entry.setValue(e.getValue()); + + return entry; + } + }; + } + + @Override + public @NotNull ObjectIterator> iterator() { + var iter = ChunkMap.this.long2ObjectEntrySet() + .fastIterator(); + + return new ObjectIterator<>() { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public ChunkEntry next() { + var e = iter.next(); + + return new ChunkEntry<>(e.getLongKey(), e.getValue()); + } + }; + } + + @Override + public int size() { + return 0; + } + } + + public static class ChunkEntry extends BasicEntry { + + public ChunkEntry() {} + + public ChunkEntry(Long key, T value) { + super(key, value); + } + + public ChunkEntry(long key, T value) { + super(key, value); + } + + public ChunkEntry(int chunkX, int chunkZ, T value) { + super(pack(chunkX, chunkZ), value); + } + + void setKey(long key) { + super.key = key; + } + + @Override + public T setValue(T value) { + T old = super.value; + super.value = value; + return old; + } + + public final int getChunkX() { + return unpackX(getLongKey()); + } + + public final int getChunkZ() { + return unpackZ(getLongKey()); + } + } + + private static final long INT = 0xFFFFFFFFL; + + public static long pack(int x, int z) { + return (z & INT) << 32 | (long) x & INT; + } + + public static int unpackX(long l) { + return (int) l; + } + + public static int unpackZ(long l) { + return (int) (l >> 32); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java index 17d02214..16503c85 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java @@ -155,23 +155,53 @@ public static Random coordsSeedRandom(long seed, int x, int y, int z) { return new Random(coordsSeedHash(seed, x, y, z)); } - public static final int MASK = 0x1FFFFF; + public static final long MASK = 0b111111111111111111111; public static final int SHIFT = 21; + public static final int X_SHIFT = SHIFT * 2; + public static final int Y_SHIFT = SHIFT; + public static final int Z_SHIFT = 0; + + private static long pack(long k, int shift) { + if (k < ~MASK || k > MASK) { + throw new IllegalArgumentException("Can only pack numbers between " + (~MASK) + ".." + MASK + " (inclusive): got " + k); + } + + // Trim off the upper bits, preserving the sign + k <<= Long.numberOfLeadingZeros(MASK); + // Shift back to the original position, but keep the sign bit on the left-most side of the long + k >>>= Long.numberOfLeadingZeros(MASK); + // Shift back up to where the long should be in the final packing + k <<= shift; + + return k; + } + + private static long unpack(long k, int shift) { + // Shift back to 0 offset + k >>>= shift; + // Only keep the good bits + k &= MASK; + // Shift the sign bit up + k <<= Long.numberOfLeadingZeros(MASK); + // Shift back to 0 offset, preserving the sign + k >>= Long.numberOfLeadingZeros(MASK); + + return k; + } public static long key(long x, long y, long z) { - return ((x & MASK) << SHIFT << SHIFT) | ((y & MASK) << SHIFT) | (z & MASK); + return pack(x, X_SHIFT) | pack(y, Y_SHIFT) | pack(z, Z_SHIFT); } public static int x(long key) { - return (int) ((key >> SHIFT >> SHIFT) & MASK); + return (int) unpack(key, X_SHIFT); } public static int y(long key) { - return (int) ((key >> SHIFT) & MASK); + return (int) unpack(key, Y_SHIFT); } public static int z(long key) { - return (int) (key & MASK); + return (int) unpack(key, Z_SHIFT); } - } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java index 245aa4e7..54f067c5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -14,9 +14,31 @@ public class DependencyGraph { + private static class DepInfo { + public final String dependency; + public final boolean optional; + + public DepInfo(String dependency, boolean optional) { + this.dependency = dependency; + this.optional = optional; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof DepInfo depInfo)) return false; + + return Objects.equals(dependency, depInfo.dependency); + } + + @Override + public int hashCode() { + return Objects.hashCode(dependency); + } + } + private final Object2ObjectOpenHashMap objects = new Object2ObjectOpenHashMap<>(); - private final Multimap dependencies = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap dependencies = MultimapBuilder.hashKeys().hashSetValues().build(); private List cachedSorted; @@ -28,11 +50,37 @@ public void addObject(String name, T value) { cachedSorted = null; } + public static final String REQUIRES = "requires:"; + public static final String REQUIRED_BY = "required-by:"; + + public void addUnparsedDependency(String object, String dep) { + Objects.requireNonNull(object, "object must not be null"); + Objects.requireNonNull(dep, "dep must not be null"); + + boolean optional = dep.endsWith("?"); + + if (optional) { + dep = dep.substring(0, dep.length() - 1); + } + + if (dep.startsWith(REQUIRES)) { + dep = dep.substring(REQUIRES.length()).trim(); + dependencies.put(object, new DepInfo(dep, optional)); + } else if (dep.startsWith(REQUIRED_BY)) { + dep = dep.substring(REQUIRED_BY.length()).trim(); + dependencies.put(dep, new DepInfo(object, optional)); + } else { + dependencies.put(object, new DepInfo(dep, optional)); + } + + cachedSorted = null; + } + public void addDependency(String object, String dependsOn) { Objects.requireNonNull(object, "object must not be null"); Objects.requireNonNull(dependsOn, "dependsOn must not be null"); - dependencies.put(object, dependsOn); + dependencies.put(object, new DepInfo(dependsOn, false)); cachedSorted = null; } @@ -40,24 +88,29 @@ public void removeDependency(String object, String dependsOn) { Objects.requireNonNull(object, "object must not be null"); Objects.requireNonNull(dependsOn, "dependsOn must not be null"); - dependencies.remove(object, dependsOn); + dependencies.remove(object, new DepInfo(dependsOn, false)); cachedSorted = null; } + public void addTarget(String targetName) { + Objects.requireNonNull(targetName, "targetName must not be null"); + + objects.put(targetName, null); + } + public List sorted() { if (cachedSorted != null) return cachedSorted; ObjectLinkedOpenHashSet path = new ObjectLinkedOpenHashSet<>(); for (String node : dependencies.keys()) { - preventCyclicDeps(node, path); + preventCyclicDeps(node, false, path); } List out = new ArrayList<>(); ObjectLinkedOpenHashSet added = new ObjectLinkedOpenHashSet<>(); ObjectLinkedOpenHashSet remaining = new ObjectLinkedOpenHashSet<>(objects.keySet()); - while (!remaining.isEmpty()) { Iterator iter = remaining.iterator(); @@ -65,8 +118,8 @@ public List sorted() { while (iter.hasNext()) { String curr = iter.next(); - for (String dep : dependencies.get(curr)) { - if (!added.contains(dep)) { + for (DepInfo dep : dependencies.get(curr)) { + if (!added.contains(dep.dependency)) { continue iterdeps; } } @@ -74,7 +127,13 @@ public List sorted() { iter.remove(); added.add(curr); - out.add(objects.get(curr)); + + T value = objects.get(curr); + + // Only targets are null + if (value != null) { + out.add(value); + } } } @@ -83,20 +142,20 @@ public List sorted() { return cachedSorted; } - private void preventCyclicDeps(String node, Set path) { + private void preventCyclicDeps(String node, boolean optional, Set path) { if (path.contains(node)) { throw new IllegalStateException(node + " has a cyclic dependency with itself. The path is: " + path.stream().reduce("", (s, s2) -> s + ", " + s2)); } - if (!objects.containsKey(node)) { + if (!optional && !objects.containsKey(node)) { throw new IllegalStateException(node + " is present in the dependency graph but does not have a matching object"); } path.add(node); - for (String deps : dependencies.get(node)) { - preventCyclicDeps(deps, path); + for (DepInfo deps : dependencies.get(node)) { + preventCyclicDeps(deps.dependency, deps.optional, path); } path.remove(node); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DoubleInterval.java b/src/main/java/com/cardinalstar/cubicchunks/util/DoubleInterval.java new file mode 100644 index 00000000..1cf561e0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DoubleInterval.java @@ -0,0 +1,15 @@ +package com.cardinalstar.cubicchunks.util; + +public class DoubleInterval { + + public final double min, max; + + public DoubleInterval(double min, double max) { + this.min = min; + this.max = max; + } + + public boolean contains(double value) { + return value >= min && value <= max; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/ObjectPooler.java b/src/main/java/com/cardinalstar/cubicchunks/util/ObjectPooler.java index 860488ae..fd9464b1 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/ObjectPooler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/ObjectPooler.java @@ -29,6 +29,10 @@ public ObjectPooler(Supplier instanceSupplier, Consumer resetter, int maxS this.availableInstances = new ObjectArrayList<>(); } + public final void clear() { + this.availableInstances.clear(); + } + public final T getInstance() { if (this.availableInstances.isEmpty()) { return this.instanceSupplier.get(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java b/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java index 596e9473..1eb2214d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/XSTR.java @@ -84,7 +84,7 @@ public double nextDouble() { * * @return the current seed */ - public synchronized long getSeed() { + public long getSeed() { return seed; } @@ -95,7 +95,7 @@ public synchronized long getSeed() { * @param seed the new seed */ @Override - public synchronized void setSeed(long seed) { + public void setSeed(long seed) { this.seed = seed; } @@ -130,7 +130,7 @@ public int next(int nbits) { double nextNextGaussian = 0; @Override - public synchronized double nextGaussian() { + public double nextGaussian() { // See Knuth, ACP, Section 3.4.1 Algorithm C. if (haveNextNextGaussian) { haveNextNextGaussian = false; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/CubicChunksSavedData.java b/src/main/java/com/cardinalstar/cubicchunks/world/CubicChunksSavedData.java new file mode 100644 index 00000000..0582db0b --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/CubicChunksSavedData.java @@ -0,0 +1,70 @@ +/* + * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). + * Copyright (c) 2015-2021 OpenCubicChunks + * Copyright (c) 2015-2021 contributors + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cardinalstar.cubicchunks.world; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.WorldSavedData; + +public class CubicChunksSavedData extends WorldSavedData { + + public int minHeight = 0, maxHeight = 256; + + public CubicChunksSavedData(String name) { + super(name); + } + + public CubicChunksSavedData(int minHeight, int maxHeight) { + this("cubicChunksData"); + + this.minHeight = minHeight; + this.maxHeight = maxHeight; + } + + public static CubicChunksSavedData get(World world) { + var data = (CubicChunksSavedData) world.mapStorage.loadData(CubicChunksSavedData.class, "cubicChunksData"); + + if (data == null) { + int maxY = ((ICubicWorldProvider) world.provider).getOriginalActualHeight(); + + data = new CubicChunksSavedData(0, maxY); + data.markDirty(); + + world.mapStorage.setData(data.mapName, data); + world.mapStorage.saveAllData(); + } + + return data; + } + + @Override + public void readFromNBT(NBTTagCompound nbt) { + // set 4 least significant bits to zero to ensure they are always multiples of 16 + minHeight = nbt.getInteger("minHeight") & ~0xF; + maxHeight = nbt.getInteger("maxHeight") & ~0xF; + } + + @Override + public void writeToNBT(NBTTagCompound compound) { + compound.setInteger("minHeight", minHeight); + compound.setInteger("maxHeight", maxHeight); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/ICubicWorldProvider.java b/src/main/java/com/cardinalstar/cubicchunks/world/ICubicWorldProvider.java index 649d63c9..4be11fbb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/ICubicWorldProvider.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/ICubicWorldProvider.java @@ -4,7 +4,7 @@ import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; public interface ICubicWorldProvider { @@ -14,7 +14,7 @@ public interface ICubicWorldProvider { * @return a new Cube generator */ @Nullable - ICubeGenerator createCubeGenerator(); + IWorldGenerator createCubeGenerator(); int getOriginalActualHeight(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/WorldSavedCubicChunksData.java b/src/main/java/com/cardinalstar/cubicchunks/world/WorldSavedCubicChunksData.java deleted file mode 100644 index 62a17a1c..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/world/WorldSavedCubicChunksData.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.world; - -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.WorldSavedData; - -import com.cardinalstar.cubicchunks.CubicChunksConfig; -import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatProviderBase; -import com.cardinalstar.cubicchunks.api.worldgen.VanillaCompatibilityGeneratorProviderBase; - -public class WorldSavedCubicChunksData extends WorldSavedData { - - public boolean isCubicChunks = false; - public int minHeight = 0, maxHeight = 256; - public ResourceLocation compatibilityGeneratorType = VanillaCompatibilityGeneratorProviderBase.DEFAULT; - public ResourceLocation storageFormat = StorageFormatProviderBase.DEFAULT; - - public WorldSavedCubicChunksData(String name) { - super(name); - } - - public WorldSavedCubicChunksData(String name, boolean isCC, int minHeight, int maxHeight) { - this(name); - if (isCC) { - this.minHeight = minHeight; - this.maxHeight = maxHeight; - isCubicChunks = true; - compatibilityGeneratorType = new ResourceLocation(CubicChunksConfig.compatibilityGeneratorType); - storageFormat = StorageFormatProviderBase.DEFAULT; - } - } - - @Override - public void readFromNBT(NBTTagCompound nbt) { - minHeight = nbt.getInteger("minHeight") & ~0xF; // set 4 least significant bits to zero to ensure they are - // always multiples of 16 - maxHeight = nbt.getInteger("maxHeight") & ~0xF; - isCubicChunks = !nbt.hasKey("isCubicChunks") || nbt.getBoolean("isCubicChunks"); - if (nbt.hasKey("compatibilityGeneratorType")) - compatibilityGeneratorType = new ResourceLocation(nbt.getString("compatibilityGeneratorType")); - else compatibilityGeneratorType = VanillaCompatibilityGeneratorProviderBase.DEFAULT; - if (nbt.hasKey("storageFormat")) storageFormat = new ResourceLocation(nbt.getString("storageFormat")); - else - // if no storage format is set, we should assume that this world was created before the custom storage API. - // therefore, it - // must be using anvil3d. - storageFormat = StorageFormatProviderBase.DEFAULT; - } - - @Override - public void writeToNBT(NBTTagCompound compound) { - compound.setInteger("minHeight", minHeight); - compound.setInteger("maxHeight", maxHeight); - compound.setBoolean("isCubicChunks", isCubicChunks); - compound.setString("compatibilityGeneratorType", compatibilityGeneratorType.toString()); - compound.setString("storageFormat", storageFormat.toString()); - } - -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java index 1dfe3399..ddc55945 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -57,11 +57,12 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.event.world.ChunkEvent; +import org.intellij.lang.annotations.MagicConstant; + import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.IHeightMap; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.server.CubeWatcher; @@ -100,15 +101,17 @@ public class Cube implements ICube { */ private boolean isModified = false; - /** - * Has the cube generator's populate() method been called for this cube? - */ - private boolean isPopulated = false; - /** - * Has the cube generator's populate() method been called for every cube potentially writing to this cube during - * population? - */ - private boolean isFullyPopulated = false; + public static final short POP_000 = 0b1; + // public static final short POP_100 = 0b10; + // public static final short POP_010 = 0b100; + // public static final short POP_110 = 0b1000; + // public static final short POP_001 = 0b10000; + // public static final short POP_101 = 0b100000; + // public static final short POP_011 = 0b1000000; + // public static final short POP_111 = 0b10000000; + public static final short POP_ALL = 0b11111111; + private short populationStatus = 0; + /** * Has the initial light map been calculated? */ @@ -307,13 +310,6 @@ protected Cube(TicketList tickets, World world, Chunk column, CubePos coords, Ex // ========Chunk vanilla methods========= // ====================================== - /** - * Returns the ExtendedBlockStorage array for this Chunk. - */ - public ExtendedBlockStorage getBlockStorageArray() { - return this.storage; - } - @Override public int getSavedLightValue(EnumSkyBlock lightType, int x, int y, int z) { ((ICubicWorldInternal) world).getLightingManager() @@ -472,7 +468,7 @@ public void tickCubeCommon(BooleanSupplier tryToTickFaster) { * @param rand - World specific Random */ public void tickCubeServer(BooleanSupplier tryToTickFaster, Random rand) { - if (!isFullyPopulated) { + if (!isFullyPopulated()) { return; } @@ -669,7 +665,7 @@ public void onCubeLoad() { ((ICubicWorldInternal) world).getLightingManager() .onCubeLoad(this); CompatHandler.onCubeLoad(new ChunkEvent.Load(getColumn())); - EVENT_BUS.post(new CubeEvent.Load(this)); + EVENT_BUS.post(new CubeEvent.Load(world, coords, this)); } @SuppressWarnings("deprecation") @@ -698,6 +694,7 @@ public void trackSurface() { * Mark this cube as no longer part of this world */ public void onCubeUnload() { + EVENT_BUS.post(new CubeEvent.Unload(this.world, this.coords, this)); ((ICubicWorldInternal) this.world).getLightingManager() .onCubeUnload(this); @@ -725,7 +722,6 @@ public void onCubeUnload() { this.world.func_147457_a(tileEntity); } ((IColumnInternal) getColumn()).removeFromStagingHeightmap(this); - EVENT_BUS.post(new CubeEvent.Unload(this)); } @Override @@ -783,44 +779,33 @@ public ICubeLightTrackingInfo getCubeLightData() { * server */ public void setClientCube() { - this.isPopulated = true; - this.isFullyPopulated = true; + this.populationStatus = POP_ALL; this.isInitialLightingDone = true; this.isSurfaceTracked = true; this.ticked = true; } - @Override - public boolean isPopulated() { - return isPopulated; + public short getPopulationStatus() { + return populationStatus; } - /** - * Mark this cube as populated. This means that this cube was passed as argument to - * {@link ICubeGenerator#populate(Cube)}. Check there for more information regarding population. - * - * @param populated whether this cube was populated - */ - public void setPopulated(boolean populated) { - this.isPopulated = populated; + public void setPopulationStatus(short populationStatus) { + this.populationStatus = populationStatus; + } + + public void markPopulated(@MagicConstant(flagsFromClass = Cube.class) short flag) { + this.populationStatus |= flag; this.isModified = true; } @Override - public boolean isFullyPopulated() { - return this.isFullyPopulated; + public boolean isPopulated() { + return (populationStatus & POP_000) != 0; } - /** - * Mark this cube as fully populated. This means that any cube potentially writing to this cube was passed as an - * argument to {@link ICubeGenerator#populate(Cube)}. Check there for more information regarding - * population. - * - * @param populated whether this cube was fully populated - */ - public void setFullyPopulated(boolean populated) { - this.isFullyPopulated = populated; - this.isModified = true; + @Override + public boolean isFullyPopulated() { + return (populationStatus & POP_ALL) == POP_ALL; } /** diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/IMutableBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/IMutableBlockView.java index fe70d3fb..479e2da3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/IMutableBlockView.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/IMutableBlockView.java @@ -21,7 +21,7 @@ default IMutableBlockView subViewMutable(Box box) { if (thisBox != null && !thisBox.contains(box)) throw new IllegalArgumentException("sub view box must be completely contained within parent view's bounds"); - return new MutableSubBlockView(this, box); + return new SubMutableBlockView(this, box); } void setBlock(int x, int y, int z, @Nonnull Block block); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeBlockView.java new file mode 100644 index 00000000..353c156f --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeBlockView.java @@ -0,0 +1,47 @@ +package com.cardinalstar.cubicchunks.world.cube.blockview; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; + +import org.jetbrains.annotations.NotNull; + +import com.cardinalstar.cubicchunks.api.util.Box; + +public class SafeBlockView implements IBlockView { + + private final Box box; + private final IBlockView next; + + public SafeBlockView(Box box, IBlockView next) { + this.box = box; + this.next = next; + } + + @Override + public @NotNull Block getBlock(int x, int y, int z) { + if (box.contains(x, y, z)) { + return next.getBlock(x, y, z); + } else { + return Blocks.air; + } + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + if (box.contains(x, y, z)) { + return next.getBlockMetadata(x, y, z); + } else { + return 0; + } + } + + @Override + public IBlockView subView(Box box) { + return new SubBlockView(this, box); + } + + @Override + public Box getBounds() { + return box; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeMutableBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeMutableBlockView.java new file mode 100644 index 00000000..a221a089 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SafeMutableBlockView.java @@ -0,0 +1,81 @@ +package com.cardinalstar.cubicchunks.world.cube.blockview; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; + +import org.jetbrains.annotations.NotNull; + +import com.cardinalstar.cubicchunks.api.util.Box; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + +public class SafeMutableBlockView implements IMutableBlockView { + + private final Box box; + private final IMutableBlockView next; + + public SafeMutableBlockView(Box box, IMutableBlockView next) { + this.box = box; + this.next = next; + } + + @Override + public void setBlock(int x, int y, int z, @NotNull Block block) { + if (box.contains(x, y, z)) { + next.setBlock(x, y, z, block); + } + } + + @Override + public void setBlockMetadata(int x, int y, int z, int meta) { + if (box.contains(x, y, z)) { + next.setBlockMetadata(x, y, z, meta); + } + } + + @Override + public void setBlock(int x, int y, int z, @NotNull Block block, int meta) { + if (box.contains(x, y, z)) { + next.setBlock(x, y, z, block, meta); + } + } + + @Override + public void setBlock(int x, int y, int z, @NotNull ImmutableBlockMeta bm) { + if (box.contains(x, y, z)) { + next.setBlock(x, y, z, bm); + } + } + + @Override + public @NotNull Block getBlock(int x, int y, int z) { + if (box.contains(x, y, z)) { + return next.getBlock(x, y, z); + } else { + return Blocks.air; + } + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + if (box.contains(x, y, z)) { + return next.getBlockMetadata(x, y, z); + } else { + return 0; + } + } + + @Override + public IBlockView subView(Box box) { + return new SubBlockView(this, box); + } + + @Override + public IMutableBlockView subViewMutable(Box box) { + return new SubMutableBlockView(this, box); + } + + @Override + public Box getBounds() { + return box; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubBlockView.java index 43117eca..7ca9b181 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubBlockView.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubBlockView.java @@ -23,22 +23,14 @@ public SubBlockView(IBlockView blockView, Box box) { public Block getBlock(int x, int y, int z) { validateCoords(x, y, z); - x += box.getX1(); - y += box.getY1(); - z += box.getZ1(); - - return blockView.getBlock(x, y, z); + return blockView.getBlock(x + box.getX1(), y + box.getY1(), z + box.getZ1()); } @Override public int getBlockMetadata(int x, int y, int z) { validateCoords(x, y, z); - x += box.getX1(); - y += box.getY1(); - z += box.getZ1(); - - return blockView.getBlockMetadata(x, y, z); + return blockView.getBlockMetadata(x + box.getX1(), y + box.getY1(), z + box.getZ1()); } public Box getBox() { @@ -56,11 +48,11 @@ public Box getBox() { } protected final void validateCoords(int x, int y, int z) { - if (x < 0 || x >= (box.getX2() - box.getX1())) throw new IllegalArgumentException( + if (x < 0 || x > box.getX2() - box.getX1()) throw new IllegalArgumentException( String.format("illegal argument: x (x=%s, x1=%s, x2=%s)", x, box.getX1(), box.getX2())); - if (y < 0 || y >= (box.getY2() - box.getY1())) throw new IllegalArgumentException( + if (y < 0 || y > box.getY2() - box.getY1()) throw new IllegalArgumentException( String.format("illegal argument: y (y=%s, y1=%s, y2=%s)", y, box.getY1(), box.getY2())); - if (z < 0 || z >= (box.getZ2() - box.getZ1())) throw new IllegalArgumentException( + if (z < 0 || z > box.getZ2() - box.getZ1()) throw new IllegalArgumentException( String.format("illegal argument: z (z=%s, z1=%s, z2=%s)", z, box.getZ1(), box.getZ2())); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/MutableSubBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubMutableBlockView.java similarity index 89% rename from src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/MutableSubBlockView.java rename to src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubMutableBlockView.java index a4bac874..631ae645 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/MutableSubBlockView.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/SubMutableBlockView.java @@ -6,11 +6,11 @@ import com.cardinalstar.cubicchunks.api.util.Box; -public class MutableSubBlockView extends SubBlockView implements IMutableBlockView { +public class SubMutableBlockView extends SubBlockView implements IMutableBlockView { private final IMutableBlockView blockView; - public MutableSubBlockView(IMutableBlockView blockView, Box box) { + public SubMutableBlockView(IMutableBlockView blockView, Box box) { super(blockView, box); this.blockView = blockView; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/UniformBlockView.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/UniformBlockView.java new file mode 100644 index 00000000..8d2bdcc1 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/blockview/UniformBlockView.java @@ -0,0 +1,26 @@ +package com.cardinalstar.cubicchunks.world.cube.blockview; + +import net.minecraft.block.Block; + +import org.jetbrains.annotations.NotNull; + +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + +public class UniformBlockView implements IBlockView { + + public final ImmutableBlockMeta bm; + + public UniformBlockView(ImmutableBlockMeta bm) { + this.bm = bm; + } + + @Override + public @NotNull Block getBlock(int x, int y, int z) { + return bm.getBlock(); + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + return bm.getBlockMeta(); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java b/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java new file mode 100644 index 00000000..e65e3af5 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java @@ -0,0 +1,62 @@ +package com.cardinalstar.cubicchunks.world.savedata; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.WorldSavedData; + +import com.cardinalstar.cubicchunks.CubicChunksConfig; +import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatFactory; +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; + +public class WorldFormatSavedData extends WorldSavedData { + + private StorageFormatFactory format; + + public WorldFormatSavedData(String name) { + super(name); + } + + public StorageFormatFactory getFormat() { + return format; + } + + @Override + public void readFromNBT(NBTTagCompound tag) { + format = StorageFormatFactory.REGISTRY.get(new UniqueIdentifier(tag.getString("format"))); + + if (format == null) { + throw new IllegalStateException("Could not load world: save format was not registered: " + tag.getString("format")); + } + } + + @Override + public void writeToNBT(NBTTagCompound tag) { + tag.setString("format", format.registryName.toString()); + } + + public static WorldFormatSavedData get(World world) { + WorldFormatSavedData data = (WorldFormatSavedData) world.mapStorage.loadData( + WorldFormatSavedData.class, + "cubicchunks.world_format"); + + if (data == null) { + data = new WorldFormatSavedData("cubicchunks.world_format"); + + UniqueIdentifier id; + + if (CubicChunksConfig.storageFormat.isEmpty()) { + id = StorageFormatFactory.DEFAULT; + } else { + id = new UniqueIdentifier(CubicChunksConfig.storageFormat); + } + + data.format = StorageFormatFactory.REGISTRY.get(id); + + data.markDirty(); + world.mapStorage.setData(data.mapName, data); + world.mapStorage.saveAllData(); + } + + return data; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java index 9f026f12..c945fc0b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java @@ -7,11 +7,11 @@ import java.util.Random; import com.cardinalstar.cubicchunks.api.util.Box; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; import com.cardinalstar.cubicchunks.util.CubePos; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; -public abstract class FeatureCubicGenerator extends SeedBasedCubicGenerator, TGen> { +public abstract class FeatureCubicGenerator extends SeedBasedCubicGenerator, TGen> { protected FeatureCubicGenerator(int range) { super(range); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java index ba497a5f..400d356d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java @@ -5,18 +5,15 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; -import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; import com.cardinalstar.cubicchunks.util.XSTR; -import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.IMutableBlockView; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public class MapGenCaveFluids implements ICubicPopulator { +public class MapGenCaveFluids implements ICubePopulator { private static final ForgeDirection[] BLOCK_MASK = { ForgeDirection.NORTH, @@ -41,17 +38,17 @@ public MapGenCaveFluids(ImmutableBlockMeta fluid, int chances) { } @Override - public void generate(World world, CubePos pos) { - if (pos.getY() >= 0) return; + public void populate(World world, Cube cube) { + if (cube.getY() >= 0) return; - IMutableBlockView cubeView = new CubeBlockView((Cube) ((ICubicWorld) world).getCubeFromCubeCoords(pos)); + IMutableBlockView cubeView = new CubeBlockView(cube); long seed = Fnv1a64.initialState(); seed = Fnv1a64.hashStep(seed, world.getSeed()); - seed = Fnv1a64.hashStep(seed, pos.getX()); - seed = Fnv1a64.hashStep(seed, pos.getY()); - seed = Fnv1a64.hashStep(seed, pos.getZ()); + seed = Fnv1a64.hashStep(seed, cube.getX()); + seed = Fnv1a64.hashStep(seed, cube.getY()); + seed = Fnv1a64.hashStep(seed, cube.getZ()); rng.setSeed(seed); @@ -80,9 +77,9 @@ public void generate(World world, CubePos pos) { cubeView.setBlock(x, y, z, fluid); - int globalX = x + pos.getX() * 16; - int globalY = y + pos.getY() * 16; - int globalZ = z + pos.getZ() * 16; + int globalX = x + cube.getX() * 16; + int globalY = y + cube.getY() * 16; + int globalZ = z + cube.getZ() * 16; fluid.getBlock().onNeighborBlockChange(world, globalX, globalY, globalZ, Blocks.air); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java index 3391d65a..b3a8bc7c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java @@ -13,12 +13,12 @@ import com.cardinalstar.cubicchunks.api.util.Box; import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.util.XSTR; -import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; +import com.cardinalstar.cubicchunks.worldgen.VanillaWorldGenerator; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; -public class MapGenCavesCubic extends FeatureCubicGenerator { +public class MapGenCavesCubic extends FeatureCubicGenerator { private final XSTR caveRandom = new XSTR(0); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java index d36c0c9d..42b0c8de 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java @@ -8,16 +8,18 @@ import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubeTerrainGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.ObjectPooler; import com.cardinalstar.cubicchunks.util.TimedCache; import com.cardinalstar.cubicchunks.util.XSTR; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.worldgen.data.FastCubePosMap; +import com.cardinalstar.cubicchunks.world.worldgen.data.MutableCubePos; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; -public abstract class SeedBasedCubicGenerator implements ICubeTerrainGenerator { +public abstract class SeedBasedCubicGenerator implements ICubeGenerator { private final XSTR rng = new XSTR(0); @@ -96,7 +98,7 @@ private long getCubeSeed(int cubeX, int cubeY, int cubeZ) { } @Override - public void generate(TGen generator, World world, Cube cube) { + public void generate(World world, Cube cube) { CubeWorldView worldView = null; setWorldSeed(world.getSeed()); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SurfacePatcherPopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SurfacePatcherPopulator.java deleted file mode 100644 index 3213e78f..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SurfacePatcherPopulator.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cardinalstar.cubicchunks.world.worldgen; - -import net.minecraft.world.World; - -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; -import com.cardinalstar.cubicchunks.util.CubePos; - -public class SurfacePatcherPopulator implements ICubicPopulator { - - @Override - public void generate(World world, CubePos pos) { - if (pos.getY() >= 0 && pos.getY() < 16) { - - } - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java index 020a5abc..ce7dbadc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -1,10 +1,16 @@ package com.cardinalstar.cubicchunks.world.worldgen; +import static com.cardinalstar.cubicchunks.api.worldgen.BuiltinWorldDecorators.CUBIC_VANILLA; + +import java.util.Random; + import net.minecraft.init.Blocks; -import com.cardinalstar.cubicchunks.api.worldgen.CubeGeneratorsRegistry; +import com.cardinalstar.cubicchunks.util.DoubleInterval; import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.worldgen.compat.DeepslateCubePopulator; +import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; +import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; import cpw.mods.fml.common.Optional; @@ -22,19 +28,44 @@ public static void init() { } private static void initVanilla() { - CubeGeneratorsRegistry.registerVanillaGenerator("caves", new MapGenCavesCubic()); +// CUBIC_VANILLA.terrain().register( +// "noodle-caves", +// new NoodleCaveGenerator(), +// "required-by:caves-all"); +// +// CUBIC_VANILLA.terrain().register( +// "spaghetti-caves", +// new SpaghettiCaveGenerator(), +// "required-by:caves-all"); - CubeGeneratorsRegistry.registerVanillaPopulator( - "water-spouts", - new MapGenCaveFluids(WATER_STILL)); + CUBIC_VANILLA.terrain().registerTarget("caves-all"); - CubeGeneratorsRegistry.registerVanillaPopulator( - "lava-spouts", - new MapGenCaveFluids(LAVA_STILL)); + // TODO: block carver + // TODO: pillar caves + // TODO: aquifers + +// CubeGeneratorsRegistry.registerVanillaPopulator( +// "water-spouts", +// new MapGenCaveFluids(WATER_STILL)); +// +// CubeGeneratorsRegistry.registerVanillaPopulator( +// "lava-spouts", +// new MapGenCaveFluids(LAVA_STILL)); } @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) private static void initEFR() { - CubeGeneratorsRegistry.registerVanillaPopulator("low-deepslate", new DeepslateCubePopulator(), "water-spouts", "lava-spouts"); + CUBIC_VANILLA.population().register( + "low-deepslate", + new DeepslateCubePopulator()); + } + + private static final double CHOOSER_SCALE = 0.01; + + public static final DoubleInterval NOODLE_CAVES = new DoubleInterval(0.7, 1); + public static final DoubleInterval PILLAR_CAVES = new DoubleInterval(0, 0.3); + + public static ScaledNoise caveChooser(Random rng) { + return new ScaledNoise(new OctavesSampler(rng, 2), CHOOSER_SCALE); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java index 770bf85e..3a5cffea 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java @@ -6,29 +6,28 @@ import org.joml.Vector3ic; -import com.cardinalstar.cubicchunks.api.worldgen.populator.ICubicPopulator; -import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; import com.cardinalstar.cubicchunks.util.Mods; -import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.IMutableBlockView; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; - import ganymedes01.etfuturum.api.DeepslateOreRegistry; import ganymedes01.etfuturum.api.mappings.RegistryMapping; -public class DeepslateCubePopulator implements ICubicPopulator { +public class DeepslateCubePopulator implements ICubePopulator { - private final LazyBlock DEEPSLATE = new LazyBlock(Mods.EtFuturumRequiem, "deepslate"); + private static final LazyBlock DEEPSLATE = new LazyBlock(Mods.EtFuturumRequiem, "deepslate"); @Override - public void generate(World world, CubePos pos) { - IMutableBlockView cube = new CubeBlockView((Cube) ((ICubicWorld) world).getCubeFromCubeCoords(pos)); + public void populate(World world, Cube cube) { + if (cube.getY() >= 0) return; + + IMutableBlockView blocks = new CubeBlockView(cube); - for (Vector3ic v : cube.getBounds()) { - Block block = cube.getBlock(v.x(), v.y(), v.z()); - int meta = cube.getBlockMetadata(v.x(), v.y(), v.z()); + for (Vector3ic v : blocks.getBounds()) { + Block block = blocks.getBlock(v.x(), v.y(), v.z()); + int meta = blocks.getBlockMetadata(v.x(), v.y(), v.z()); if (block == Blocks.stone) { block = DEEPSLATE.getBlock(); @@ -42,8 +41,8 @@ public void generate(World world, CubePos pos) { } } - cube.setBlock(v.x(), v.y(), v.z(), block); - cube.setBlockMetadata(v.x(), v.y(), v.z(), meta); + blocks.setBlock(v.x(), v.y(), v.z(), block); + blocks.setBlockMetadata(v.x(), v.y(), v.z(), meta); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java similarity index 97% rename from src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java index 3d2bf3b1..4dad1d96 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FastCubePosMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java @@ -1,4 +1,4 @@ -package com.cardinalstar.cubicchunks.world.worldgen; +package com.cardinalstar.cubicchunks.world.worldgen.data; import java.util.AbstractMap; import java.util.AbstractSet; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MutableCubePos.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/MutableCubePos.java similarity index 86% rename from src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MutableCubePos.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/MutableCubePos.java index 44fb8334..fe194121 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MutableCubePos.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/MutableCubePos.java @@ -1,15 +1,18 @@ -package com.cardinalstar.cubicchunks.world.worldgen; +package com.cardinalstar.cubicchunks.world.worldgen.data; + +import org.jetbrains.annotations.ApiStatus; import com.cardinalstar.cubicchunks.util.Bits; import com.cardinalstar.cubicchunks.util.CubePos; /** * This is a hacky subclass of CubePos. It's only meant for accessing the TimedCache, and nothing else. The CubePos - * fields aren't initialized properly, since they're all final, but the various get... methods work properly. This will + * fields aren't initialized properly, since they're all final, but the various get### methods work properly. This will * always return the same hash as a CubePos, and it will always equal a CubePos with the same coord. The opposite is not * true - a CubePos will never equal a MutableCubePos. */ -class MutableCubePos extends CubePos { +@ApiStatus.Internal +public class MutableCubePos extends CubePos { public int x; public int y; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java new file mode 100644 index 00000000..4501b53e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java @@ -0,0 +1,176 @@ +package com.cardinalstar.cubicchunks.world.worldgen.data; + +import java.util.EnumMap; +import java.util.concurrent.TimeUnit; + +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; + +import com.cardinalstar.cubicchunks.async.TaskPool; +import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.util.ObjectPooler; +import com.cardinalstar.cubicchunks.util.XSTR; +import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; +import com.github.bsideup.jabel.Desugar; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class NoisePrecalculator & SamplerFactory> { + + private final ObjectPooler dataPool = new ObjectPooler<>(NoiseData::new, null, 1024); + + private final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(30, TimeUnit.SECONDS) + .maximumSize(1024) + .removalListener(notification -> { + if (notification.getCause() != RemovalCause.EXPLICIT) { + //noinspection unchecked + releaseData((NoiseData) notification.getValue()); + } + }) + .build(); + + private final Int2ObjectOpenHashMap> samplers = new Int2ObjectOpenHashMap<>(); + private final Class layers; + private final int layerCount; + private final int seed; + + public NoisePrecalculator(Class layers, int seed) { + this.layers = layers; + this.layerCount = layers.getEnumConstants().length; + this.seed = seed; + } + + private EnumMap createSamplers(long seed, String dimName) { + EnumMap samplers = new EnumMap<>(layers); + + for (TLayer layer : layers.getEnumConstants()) { + long hash = Fnv1a64.initialState(); + hash = Fnv1a64.hashStep(hash, seed); + hash = Fnv1a64.hashStep(hash, dimName); + hash = Fnv1a64.hashStep(hash, layer.name()); + hash = Fnv1a64.hashStep(hash, this.seed); + + samplers.put(layer, layer.createSampler(new XSTR(hash))); + } + + return samplers; + } + + private NoiseData getData() { + synchronized (dataPool) { + return dataPool.getInstance(); + } + } + + public void releaseData(NoiseData data) { + synchronized (dataPool) { + dataPool.releaseInstance(data); + } + } + + private @NotNull EnumMap getSamplers(World world) { + int worldId = world.provider.dimensionId; + + EnumMap samplers = this.samplers.get(worldId); + + if (samplers == null) { + samplers = createSamplers(world.getSeed(), world.provider.getDimensionName()); + this.samplers.put(worldId, samplers); + } + + return samplers; + } + + public void submitPrecalculate(World world, int cubeX, int cubeY, int cubeZ) { + EnumMap samplers = getSamplers(world); + + TaskKey key = new TaskKey(world.provider.dimensionId, cubeX, cubeY, cubeZ); + + TaskPool.submit(new Task3D(samplers, key)); + } + + public NoiseData takeSampler(World world, int cubeX, int cubeY, int cubeZ) { + TaskKey key = new TaskKey(world.provider.dimensionId, cubeX, cubeY, cubeZ); + + NoiseData data = cache.asMap().remove(key); + + if (data == null) { + data = getData(); + + compute3d(getSamplers(world), cubeX << 4, cubeY << 4, cubeZ << 4, data); + } + + return data; + } + + private void compute3d(EnumMap samplers, int wx, int wy, int wz, NoiseData data) { + samplers.forEach((layer, sampler) -> { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + double value = sampler.sample(wx + x, wy + y, wz + z); + + data.put(layer, x, y, z, value); + } + } + } + }); + } + + private class Task3D implements ITask { + + private final EnumMap samplers; + private final TaskKey key; + + public Task3D(EnumMap samplers, TaskKey key) { + this.samplers = samplers; + this.key = key; + } + + @Override + public Void call() { + NoiseData data = getData(); + + compute3d(samplers, key.x() << 4, key.y() << 4, key.z() << 4, data); + + cache.put(key, data); + + return null; + } + } + + @Desugar + private record TaskKey(int worldId, int x, int y, int z) { + + } + + public class NoiseData { + + private final double[] data = new double[16 * 16 * 16 * layerCount]; + + final void put(TLayer layer, int x, int y, double value) { + data[index(layer, x, y, 0)] = value; + } + + final void put(TLayer layer, int x, int y, int z, double value) { + data[index(layer, x, y, z)] = value; + } + + public final double sample(TLayer layer, int x, int y) { + return sample(layer, x, y, 0); + } + + public final double sample(TLayer layer, int x, int y, int z) { + return data[index(layer, x, y, z)]; + } + + private static > int index(TLayer layer, int x, int y, int z) { + return x | (y << 4) | (z << 8) | (layer.ordinal() << 12); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/SamplerFactory.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/SamplerFactory.java new file mode 100644 index 00000000..ef99eefe --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/SamplerFactory.java @@ -0,0 +1,10 @@ +package com.cardinalstar.cubicchunks.world.worldgen.data; + +import java.util.Random; + +import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; + +public interface SamplerFactory { + + NoiseSampler createSampler(Random rng); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java new file mode 100644 index 00000000..824b5446 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java @@ -0,0 +1,81 @@ +package com.cardinalstar.cubicchunks.world.worldgen.modern; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; +import com.cardinalstar.cubicchunks.world.worldgen.WorldGenerators; +import com.cardinalstar.cubicchunks.world.worldgen.data.NoisePrecalculator; +import com.cardinalstar.cubicchunks.world.worldgen.data.SamplerFactory; +import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; +import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; +import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; + +public class NoodleCaveGenerator implements ICubeGenerator { + + private static final double CARVE_THRESHOLD = 0.25; + private static final double SCALE = 0.04; + + private enum Layers implements SamplerFactory { + Chooser { + @Override + public NoiseSampler createSampler(Random rng) { + return WorldGenerators.caveChooser(rng); + } + }, + A { + @Override + public NoiseSampler createSampler(Random rng) { + return new ScaledNoise(new OctavesSampler(rng, 1), SCALE); + } + }, + B { + @Override + public NoiseSampler createSampler(Random rng) { + return new ScaledNoise(new OctavesSampler(rng, 2), SCALE); + } + }; + } + + private final NoisePrecalculator noise = new NoisePrecalculator<>(Layers.class, 2); + + @Override + public void pregenerate(World world, CubePos pos) { + noise.submitPrecalculate(world, pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public void generate(World world, Cube cube) { + var data = noise.takeSampler(world, cube.getX(), cube.getY(), cube.getZ()); + + CubeBlockView view = new CubeBlockView(cube); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 16; y++) { + Block existing = view.getBlock(x, y, z); + + if (existing != Blocks.stone) continue; + + double cave = data.sample(Layers.Chooser, x, y, z); + if (!WorldGenerators.NOODLE_CAVES.contains(cave)) continue; + + double a = data.sample(Layers.A, x, y, z); + double b = data.sample(Layers.B, x, y, z); + + double value = a * a + b * b; + + if (value <= CARVE_THRESHOLD) { + view.setBlock(x, y, z, Blocks.air, 0); + } + } + } + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java new file mode 100644 index 00000000..c4f8da4a --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java @@ -0,0 +1,73 @@ +package com.cardinalstar.cubicchunks.world.worldgen.modern; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; +import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; +import com.cardinalstar.cubicchunks.world.worldgen.data.NoisePrecalculator; +import com.cardinalstar.cubicchunks.world.worldgen.data.SamplerFactory; +import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; +import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; +import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; + +public class SpaghettiCaveGenerator implements ICubeGenerator { + + private static final double CARVE_THRESHOLD = 0.01; + private static final double SCALE = 0.01; + + private enum Layers implements SamplerFactory { + A { + @Override + public NoiseSampler createSampler(Random rng) { + return new ScaledNoise(new OctavesSampler(rng, 3), SCALE); + } + }, + B { + @Override + public NoiseSampler createSampler(Random rng) { + return new ScaledNoise(new OctavesSampler(rng, 3), SCALE); + } + }; + } + + private final NoisePrecalculator noise = new NoisePrecalculator<>(Layers.class, 5); + + @Override + public void pregenerate(World world, CubePos pos) { + noise.submitPrecalculate(world, pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public void generate(World world, Cube cube) { + var data = noise.takeSampler(world, cube.getX(), cube.getY(), cube.getZ()); + + CubeBlockView view = new CubeBlockView(cube); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 16; y++) { + Block existing = view.getBlock(x, y, z); + + if (existing != Blocks.stone) continue; + + double a = data.sample(Layers.A, x, y, z); + double b = data.sample(Layers.B, x, y, z); + + double value = a * a + b * b; + + if (value <= CARVE_THRESHOLD) { + view.setBlock(x, y, z, Blocks.air, 0); + } + } + } + } + + noise.releaseData(data); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java new file mode 100644 index 00000000..84dec547 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.world.worldgen.noise; + +public interface BlockNoiseSampler { + + double sample(int x, int y); + double sample(int x, int y, int z); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java new file mode 100644 index 00000000..a440d3b5 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.world.worldgen.noise; + +public interface NoiseSampler { + + double sample(double x, double y); + double sample(double x, double y, double z); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/OctavesSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/OctavesSampler.java new file mode 100644 index 00000000..9e171ea7 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/OctavesSampler.java @@ -0,0 +1,54 @@ +package com.cardinalstar.cubicchunks.world.worldgen.noise; + +import java.util.Random; +import java.util.function.Supplier; + +public class OctavesSampler implements NoiseSampler { + + private final NoiseSampler[] octaves; + private final double[] amplitudes, scales; + + public OctavesSampler(Supplier samplers, int octaves) { + this.octaves = new NoiseSampler[octaves]; + this.amplitudes = new double[octaves]; + this.scales = new double[octaves]; + + for (int i = 0; i < octaves; i++) { + this.octaves[i] = samplers.get(); + this.amplitudes[i] = 1d / Math.pow(2d, i); + this.scales[i] = Math.pow(2d, i); + } + } + + public OctavesSampler(Random rng, int octaves) { + this(() -> new SimplexNoiseSampler(rng), octaves); + } + + @Override + public double sample(double x, double y) { + double value = 0; + + for (int i = 0, octavesLength = octaves.length; i < octavesLength; i++) { + NoiseSampler sampler = octaves[i]; + double scale = scales[i]; + + value += sampler.sample(x * scale, y * scale) * amplitudes[i]; + } + + return value; + } + + @Override + public double sample(double x, double y, double z) { + double value = 0; + + for (int i = 0, octavesLength = octaves.length; i < octavesLength; i++) { + NoiseSampler sampler = octaves[i]; + double scale = scales[i]; + + value += sampler.sample(x * scale, y * scale, z * scale) * amplitudes[i]; + } + + return value; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/ScaledNoise.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/ScaledNoise.java new file mode 100644 index 00000000..814422f4 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/ScaledNoise.java @@ -0,0 +1,30 @@ +package com.cardinalstar.cubicchunks.world.worldgen.noise; + +public class ScaledNoise implements NoiseSampler { + + private final NoiseSampler base; + private final double scaleX; + private final double scaleY; + private final double scaleZ; + + public ScaledNoise(NoiseSampler base, double scaleX, double scaleY, double scaleZ) { + this.base = base; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + } + + public ScaledNoise(NoiseSampler base, double scale) { + this(base, scale, scale, scale); + } + + @Override + public double sample(double x, double y) { + return base.sample(x * scaleX, y * scaleY); + } + + @Override + public double sample(double x, double y, double z) { + return base.sample(x * scaleX, y * scaleY, z * scaleZ); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java new file mode 100644 index 00000000..48a26c76 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java @@ -0,0 +1,183 @@ +package com.cardinalstar.cubicchunks.world.worldgen.noise; + +import java.util.Random; + +import net.minecraft.util.MathHelper; + +public class SimplexNoiseSampler implements NoiseSampler { + + protected static final int[][] GRADIENTS = new int[][]{{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; + private static final double SQRT_3 = Math.sqrt(3.0D); + private static final double SKEW_FACTOR_2D; + private static final double UNSKEW_FACTOR_2D; + private final int[] permutations = new int[512]; + public final double originX; + public final double originY; + public final double originZ; + + public SimplexNoiseSampler(Random random) { + this.originX = random.nextDouble() * 256.0D; + this.originY = random.nextDouble() * 256.0D; + this.originZ = random.nextDouble() * 256.0D; + + for (int i = 0; i < 256; i++) { + this.permutations[i] = i; + } + + for (int i = 0; i < 256; ++i) { + int k = random.nextInt(256 - i); + int l = this.permutations[i]; + this.permutations[i] = this.permutations[k + i]; + this.permutations[k + i] = l; + } + } + + private int getGradient(int hash) { + return this.permutations[hash & 255]; + } + + protected static double dot(int[] gArr, double x, double y, double z) { + return (double) gArr[0] * x + (double) gArr[1] * y + (double) gArr[2] * z; + } + + private double grad(int hash, double x, double y, double z, double distance) { + double d = distance - x * x - y * y - z * z; + double f; + if (d < 0.0D) { + f = 0.0D; + } else { + d *= d; + f = d * d * dot(GRADIENTS[hash], x, y, z); + } + + return f; + } + + @Override + public double sample(double x, double y) { + double d = (x + y) * SKEW_FACTOR_2D; + int i = MathHelper.floor_double(x + d); + int j = MathHelper.floor_double(y + d); + double e = (double) (i + j) * UNSKEW_FACTOR_2D; + double f = (double) i - e; + double g = (double) j - e; + double h = x - f; + double k = y - g; + byte n; + byte o; + if (h > k) { + n = 1; + o = 0; + } else { + n = 0; + o = 1; + } + + double p = h - (double) n + UNSKEW_FACTOR_2D; + double q = k - (double) o + UNSKEW_FACTOR_2D; + double r = h - 1.0D + 2.0D * UNSKEW_FACTOR_2D; + double s = k - 1.0D + 2.0D * UNSKEW_FACTOR_2D; + int t = i & 255; + int u = j & 255; + int v = this.getGradient(t + this.getGradient(u)) % 12; + int w = this.getGradient(t + n + this.getGradient(u + o)) % 12; + int z = this.getGradient(t + 1 + this.getGradient(u + 1)) % 12; + double aa = this.grad(v, h, k, 0.0D, 0.5D); + double ab = this.grad(w, p, q, 0.0D, 0.5D); + double ac = this.grad(z, r, s, 0.0D, 0.5D); + return 70.0D * (aa + ab + ac); + } + + @Override + public double sample(double x, double y, double z) { + double e = (x + y + z) * 0.3333333333333333D; + int i = MathHelper.floor_double(x + e); + int j = MathHelper.floor_double(y + e); + int k = MathHelper.floor_double(z + e); + double g = (double) (i + j + k) * 0.16666666666666666D; + double h = (double) i - g; + double l = (double) j - g; + double m = (double) k - g; + double n = x - h; + double o = y - l; + double p = z - m; + byte w; + byte aa; + byte ab; + byte ac; + byte ad; + byte bc; + if (n >= o) { + if (o >= p) { + w = 1; + aa = 0; + ab = 0; + ac = 1; + ad = 1; + bc = 0; + } else if (n >= p) { + w = 1; + aa = 0; + ab = 0; + ac = 1; + ad = 0; + bc = 1; + } else { + w = 0; + aa = 0; + ab = 1; + ac = 1; + ad = 0; + bc = 1; + } + } else if (o < p) { + w = 0; + aa = 0; + ab = 1; + ac = 0; + ad = 1; + bc = 1; + } else if (n < p) { + w = 0; + aa = 1; + ab = 0; + ac = 0; + ad = 1; + bc = 1; + } else { + w = 0; + aa = 1; + ab = 0; + ac = 1; + ad = 1; + bc = 0; + } + + double bd = n - (double) w + 0.16666666666666666D; + double be = o - (double) aa + 0.16666666666666666D; + double bf = p - (double) ab + 0.16666666666666666D; + double bg = n - (double) ac + 0.3333333333333333D; + double bh = o - (double) ad + 0.3333333333333333D; + double bi = p - (double) bc + 0.3333333333333333D; + double bj = n - 1.0D + 0.5D; + double bk = o - 1.0D + 0.5D; + double bl = p - 1.0D + 0.5D; + int bm = i & 255; + int bn = j & 255; + int bo = k & 255; + int bp = this.getGradient(bm + this.getGradient(bn + this.getGradient(bo))) % 12; + int bq = this.getGradient(bm + w + this.getGradient(bn + aa + this.getGradient(bo + ab))) % 12; + int br = this.getGradient(bm + ac + this.getGradient(bn + ad + this.getGradient(bo + bc))) % 12; + int bs = this.getGradient(bm + 1 + this.getGradient(bn + 1 + this.getGradient(bo + 1))) % 12; + double bt = this.grad(bp, n, o, p, 0.6D); + double bu = this.grad(bq, bd, be, bf, 0.6D); + double bv = this.grad(br, bg, bh, bi, 0.6D); + double bw = this.grad(bs, bj, bk, bl, 0.6D); + return 32.0D * (bt + bu + bv + bw); + } + + static { + SKEW_FACTOR_2D = 0.5D * (SQRT_3 - 1.0D); + UNSKEW_FACTOR_2D = (3.0D - SQRT_3) / 6.0D; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java new file mode 100644 index 00000000..c12b9709 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -0,0 +1,214 @@ +package com.cardinalstar.cubicchunks.world.worldgen.vanilla; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import net.minecraft.world.gen.NoiseGeneratorOctaves; + +import com.cardinalstar.cubicchunks.CubicChunks; +import com.cardinalstar.cubicchunks.async.TaskPool; +import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.util.ObjectPooler; +import com.github.bsideup.jabel.Desugar; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements PrecalculableNoise { + + private boolean initialized = false; + private int xspan, yspan, zspan; + private double xscale, yscale, zscale; + private int misses, misses2, hits; + private long lastMessage = System.nanoTime(); + + private final ObjectPooler dataPool = new ObjectPooler<>(NoiseData::new, null, 1024); + + private final Object paramLock = new Object(); + private final Object cacheLock = new Object(); + + private final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .maximumSize(1024) + .removalListener(notification -> releaseData((NoiseData) notification.getValue())) + .build(); + + public PrecalcedVanillaOctaves(Random rng, int octaves) { + super(rng, octaves); + } + + private static boolean diff(double a, double b) { + return Math.abs(a - b) > 0.0001; + } + + @Override + public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int blockZ, int sx, int sy, int sz, double scaleX, double scaleY, double scaleZ) { + long now = System.nanoTime(); + + if ((now - lastMessage) > 5e9) { + lastMessage = now; + CubicChunks.LOGGER.info("Hits: {} Misses: {}", hits, misses2); + hits = 0; + misses2 = 0; + } + + synchronized (paramLock) { + if (!initialized) { + initialized = true; + + xspan = sx; + yspan = sy; + zspan = sz; + xscale = scaleX; + yscale = scaleY; + zscale = scaleZ; + } + + // This should never happen because all vanilla noisegens are given constant params, but you never know what mods could do + if (sx != xspan || sy != yspan || sz != zspan || diff(scaleX, xscale) || diff(scaleY, yscale) || diff(scaleZ, zscale)) { + misses++; + + if (misses > 20) { + CubicChunks.LOGGER.info("Parameters for noisegen changed: resetting"); + misses = 0; + + xspan = sx; + yspan = sy; + zspan = sz; + xscale = scaleX; + yscale = scaleY; + zscale = scaleZ; + + dataPool.clear(); + } + } + } + + if (data == null) data = new double[sx * sy * sz]; + + NoiseData cached; + + synchronized (cacheLock) { + cached = cache.getIfPresent(new TaskKey(blockX, blockY, blockZ)); + } + + if (cached != null && cached.matches()) { + System.arraycopy(cached.data, 0, data, 0, cached.data.length); + + hits++; + return data; + } + + misses2++; + return super.generateNoiseOctaves(data, blockX, blockY, blockZ, sx, sy, sz, scaleX, scaleY, scaleZ); + } + + private NoiseData getData() { + synchronized (dataPool) { + return dataPool.getInstance(); + } + } + + private void releaseData(NoiseData data) { + synchronized (dataPool) { + if (!data.matches()) return; + + dataPool.releaseInstance(data); + } + } + + @Override + public void precalculate(int blockX, int blockY, int blockZ) { + Task3D task; + + synchronized (paramLock) { + if (!initialized) return; + + TaskKey key = new TaskKey(blockX, blockY, blockZ); + + task = new Task3D(key); + } + + TaskPool.submit(task); + } + + @Desugar + + private record TaskKey(int x, int y, int z) { + + } + + private class Task3D implements ITask { + + private final TaskKey key; + + private final int xspan, yspan, zspan; + private final double xscale, yscale, zscale; + + public Task3D(TaskKey key) { + this.key = key; + this.xspan = PrecalcedVanillaOctaves.this.xspan; + this.yspan = PrecalcedVanillaOctaves.this.yspan; + this.zspan = PrecalcedVanillaOctaves.this.zspan; + this.xscale = PrecalcedVanillaOctaves.this.xscale; + this.yscale = PrecalcedVanillaOctaves.this.yscale; + this.zscale = PrecalcedVanillaOctaves.this.zscale; + } + + @Override + public Void call() { + NoiseData data = getData(); + + data.xspan = this.xspan; + data.yspan = this.yspan; + data.zspan = this.zspan; + data.xscale = this.xscale; + data.yscale = this.yscale; + data.zscale = this.zscale; + + PrecalcedVanillaOctaves.super.generateNoiseOctaves( + data.data, + key.x, key.y, key.z, + xspan, yspan, zspan, + xscale, yscale, zscale); + + cache.put(key, data); + + return null; + } + } + + private class NoiseData { + + public final double[] data; + + public int xspan, yspan, zspan; + public double xscale, yscale, zscale; + + public NoiseData() { + this.data = new double[PrecalcedVanillaOctaves.this.xspan * PrecalcedVanillaOctaves.this.yspan * PrecalcedVanillaOctaves.this.zspan]; + } + + final boolean matches() { + if (this.xspan != PrecalcedVanillaOctaves.this.xspan) return false; + if (this.yspan != PrecalcedVanillaOctaves.this.yspan) return false; + if (this.zspan != PrecalcedVanillaOctaves.this.zspan) return false; + if (diff(this.xscale, PrecalcedVanillaOctaves.this.xscale)) return false; + if (diff(this.yscale, PrecalcedVanillaOctaves.this.yscale)) return false; + if (diff(this.zscale, PrecalcedVanillaOctaves.this.zscale)) return false; + + return true; + } + + final void put(int x, int y, int z, double value) { + data[index(x, y, z)] = value; + } + + public final double sample(int x, int y, int z) { + return data[index(x, y, z)]; + } + + private int index(int x, int y, int z) { + return x + (y * xspan) + (z * xspan * yspan); + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalculableNoise.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalculableNoise.java new file mode 100644 index 00000000..321de2d2 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalculableNoise.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.world.worldgen.vanilla; + +/// A noise layer that can be precalculated +public interface PrecalculableNoise { + + void precalculate(int blockX, int blockY, int blockZ); + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java similarity index 54% rename from src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java rename to src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index 9fdfb33a..f8843c98 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -20,6 +20,8 @@ */ package com.cardinalstar.cubicchunks.worldgen; +import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -31,47 +33,51 @@ import net.minecraft.block.Block; import net.minecraft.entity.EnumCreatureType; import net.minecraft.init.Blocks; +import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.ChunkPosition; import net.minecraft.world.World; import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.IChunkProvider; +import org.joml.Vector3i; import org.joml.Vector3ic; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.util.Box; -import com.cardinalstar.cubicchunks.api.worldgen.CubeGeneratorsRegistry; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +import com.cardinalstar.cubicchunks.api.world.Precalculable; +import com.cardinalstar.cubicchunks.api.worldgen.GenerationResult; +import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.IWorldDecorator; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.mixin.early.common.IGameRegistry; +import com.cardinalstar.cubicchunks.server.CubeProviderServer; +import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.server.chunkio.ICubeLoader; +import com.cardinalstar.cubicchunks.server.chunkio.IPreloadFailureDelegate; import com.cardinalstar.cubicchunks.util.CompatHandler; import com.cardinalstar.cubicchunks.util.Coords; -import com.cardinalstar.cubicchunks.util.XSTR; +import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.ICubicWorld; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.blockview.ChunkArrayBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.ChunkBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.IBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.IMutableBlockView; +import com.cardinalstar.cubicchunks.world.cube.blockview.SafeMutableBlockView; +import com.cardinalstar.cubicchunks.world.cube.blockview.UniformBlockView; import com.github.bsideup.jabel.Desugar; -import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; import com.gtnewhorizon.gtnhlib.util.data.BlockMeta; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; - -import cpw.mods.fml.common.IWorldGenerator; -import cpw.mods.fml.common.registry.GameRegistry; +import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; -import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; /** * A cube generator that tries to mirror vanilla world generation. Cubes in the normal world range will be copied from a @@ -79,31 +85,20 @@ * topmost/bottommost layers. */ @ParametersAreNonnullByDefault -public class VanillaCompatibilityGenerator implements ICubeGenerator { - - private static final Box BOTTOM_BEDROCK_LAYER = Box.horizontalChunkSlice(0, 8); +public class VanillaWorldGenerator implements IWorldGenerator, IPreloadFailureDelegate { @Desugar record FillerInfo(ImmutableBlockMeta filler) {} - private final int worldHeightBlocks; - private final int worldHeightCubes; - private final Box topBedrockLayer; - @Nonnull private final IChunkProvider vanilla; @Nonnull private final World world; - /** - * Last chunk that was generated from the vanilla world gen - */ - private Chunk lastChunk; - private IMutableBlockView lastChunkView; - /** - * We generate all the chunks in the vanilla range at once. This variable prevents infinite recursion - */ - private boolean optimizationHack; - private BiomeGenBase[] biomes; + @Nonnull + private final IWorldDecorator decorator; + + private final int worldHeightBlocks; + private final int worldHeightCubes; private FillerInfo bottom, top; @@ -113,18 +108,21 @@ record FillerInfo(ImmutableBlockMeta filler) {} * @param vanilla The vanilla generator to mirror * @param world The world in which cubes are being generated */ - public VanillaCompatibilityGenerator(IChunkProvider vanilla, World world) { + public VanillaWorldGenerator(IChunkProvider vanilla, World world, IWorldDecorator decorator) { this.vanilla = vanilla; this.world = world; + this.decorator = decorator; worldHeightBlocks = ((ICubicWorld) this.world).getMaxGenerationHeight(); worldHeightCubes = Coords.blockCeilToCube(worldHeightBlocks); - topBedrockLayer = Box.horizontalChunkSlice(worldHeightBlocks - 8, 8); } private ICubeLoader getCubeLoader() { - return ((ICubicWorldInternal.Server) world).getCubeCache() - .getCubeLoader(); + return getCubeProviderServer().getCubeLoader(); + } + + private CubeProviderServer getCubeProviderServer() { + return ((ICubicWorldInternal.Server) world).getCubeCache(); } private FillerInfo getBottomFillerInfo() { @@ -213,15 +211,35 @@ private FillerInfo analyzeTopFiller(IBlockView blockView) { } @Override - public void generateColumn(Chunk column) { + public void onColumnPreloadFailed(ChunkCoordIntPair pos) { + // Do nothing, columns contain very little currently and don't need to be pre-generated + } + + @Override + public GenerationResult provideColumn(World world, int columnX, int columnZ) { + Pair data = getVanillaChunkView(columnX, columnZ); - this.biomes = this.world.getWorldChunkManager() - .loadBlockGeneratorData(this.biomes, column.xPosition * 16, column.zPosition * 16, 16, 16); + List cubes = new ArrayList<>(); - byte[] biomeArray = column.getBiomeArray(); - for (int i = 0; i < biomeArray.length; ++i) { - biomeArray[i] = (byte) this.biomes[i].biomeID; + // Ceiling div by 16 + int heightCubes = (data.right().getBounds().getSizeY() + 15) >> 4; + + for (int y = 0; y < heightCubes; y++) { + Cube c = new Cube(data.left(), y, data.right().subView(Box.horizontalChunkSlice(y << 4, 16))); + + try { + decorator.generate(world, c); + } catch (Throwable t) { + CubicChunks.LOGGER + .error("Could not run generation for cube {},{},{}", columnX, y, columnZ, t); + } + + cubes.add(c); } + + ((IColumnInternal) data.left()).setColumn(true); + + return new GenerationResult<>(data.left(), null, cubes); } @Override @@ -230,7 +248,7 @@ public void recreateStructures(Chunk column) { } @Override - public Cube provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ) { + public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ) { try { WorldgenHangWatchdog.startWorldGen(); @@ -239,116 +257,87 @@ public Cube provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ) { if (cubeY < 0) { FillerInfo fillerInfo = getBottomFillerInfo(); - Block[] blocks = new Block[4096]; - int[] blockMeta = new int[4096]; - - cubeData = new ChunkArrayBlockView( - 16, - 16, - 16, - ObjectArrayList.wrap(blocks), - IntArrayList.wrap(blockMeta)); - - // Fill with bottom block - ((IMutableBlockView) cubeData).fill(fillerInfo.filler); + cubeData = new UniformBlockView(fillerInfo.filler); } else if (cubeY >= worldHeightCubes) { FillerInfo fillerInfo = getTopFillerInfo(); - Block[] blocks = new Block[4096]; - int[] blockMeta = new int[4096]; + cubeData = new UniformBlockView(fillerInfo.filler); + } else { + Pair data = getVanillaChunkView(cubeX, cubeZ); - cubeData = new ChunkArrayBlockView( - 16, - 16, - 16, - ObjectArrayList.wrap(blocks), - IntArrayList.wrap(blockMeta)); + Cube self = null; + List sideEffects = new ArrayList<>(); - // Fill with top block - ((IMutableBlockView) cubeData).fill(fillerInfo.filler); - } else { - cubeData = getVanillaChunkSlice(cubeX, cubeY, cubeZ); + // Ceiling div by 16 + int heightCubes = (data.right().getBounds().getSizeY() + 15) >> 4; + + for (int y = 0; y < heightCubes; y++) { + Cube c = new Cube(chunk, y, data.right().subView(Box.horizontalChunkSlice(y << 4, 16))); + + try { + decorator.generate(world, c); + } catch (Throwable t) { + CubicChunks.LOGGER + .error("Could not run generation for cube {},{},{}", cubeX, y, cubeZ, t); + } + + if (y == cubeY) { + self = c; + } else { + sideEffects.add(c); + } + } + + return new GenerationResult<>(self, Collections.singletonList(data.left()), sideEffects); } Cube cube = new Cube(chunk, cubeY, cubeData); try { - CubeGeneratorsRegistry.generateVanillaCube(this, world, cube); + decorator.generate(world, cube); } catch (Throwable t) { CubicChunks.LOGGER - .error("Could not run non-vanilla generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); + .error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); } - return cube; + return new GenerationResult<>(cube); } finally { WorldgenHangWatchdog.endWorldGen(); } } - private IBlockView getVanillaChunkSlice(int cubeX, int cubeY, int cubeZ) { - // Make vanilla generate a chunk for us to copy - if (lastChunk == null || lastChunk.xPosition != cubeX || lastChunk.zPosition != cubeZ) { - long hash = Fnv1a64.initialState(); - hash = Fnv1a64.hashStep(hash, world.getSeed()); - hash = Fnv1a64.hashStep(hash, cubeX); - hash = Fnv1a64.hashStep(hash, cubeZ); - - XSTR rand = new XSTR(hash); - - generateVanillaChunk(cubeX, cubeZ, rand); - } - - // Generate all cubes in the current vanilla chunk, since we've already done the work to generate their terrain - // (vanilla generators can only generate a whole chunk at a time) - if (!optimizationHack) { - optimizationHack = true; - for (int y = worldHeightCubes - 1; y >= 0; y--) { - if (y == cubeY) { - continue; - } - - getCubeLoader().getCube(cubeX, y, cubeZ, ICubeProviderServer.Requirement.GENERATE); - } - optimizationHack = false; - } - - return lastChunkView.subView(Box.horizontalChunkSlice(cubeY * 16, 16)); - } - - private void generateVanillaChunk(int cubeX, int cubeZ, Random rand) { + private Pair getVanillaChunkView(int cubeX, int cubeZ) { if (CubicChunksConfig.optimizedCompatibilityGenerator) { try (ICubicWorldInternal.CompatGenerationScope ignored = ((ICubicWorldInternal.Server) world) .doCompatibilityGeneration()) { - lastChunk = vanilla.provideChunk(cubeX, cubeZ); + Chunk chunk = vanilla.provideChunk(cubeX, cubeZ); - Block[] compatBlocks = ((IColumnInternal) lastChunk).getCompatGenerationBlockArray(); - byte[] compatBlockMeta = ((IColumnInternal) lastChunk).getCompatGenerationByteArray(); + Block[] compatBlocks = ((IColumnInternal) chunk).getCompatGenerationBlockArray(); + byte[] compatBlockMeta = ((IColumnInternal) chunk).getCompatGenerationByteArray(); if (compatBlocks == null || compatBlockMeta == null) { CubicChunks.LOGGER.error("Optimized compatibility generation failed, disabling..."); CubicChunksConfig.optimizedCompatibilityGenerator = false; } else { - lastChunkView = new ChunkArrayBlockView( + int lastChunkHeight = compatBlocks.length >> 8; + + IMutableBlockView view = new ChunkArrayBlockView( 16, - worldHeightBlocks, + lastChunkHeight, 16, wrapBlockArray(compatBlocks), wrapByteArray(compatBlockMeta)); - removeBedrock(lastChunkView, rand); + view = new SafeMutableBlockView(Box.horizontalChunkSlice(0, worldHeightBlocks), view); - return; + return Pair.of(chunk, view); } } } - lastChunk = vanilla.provideChunk(cubeX, cubeZ); + Chunk chunk = vanilla.provideChunk(cubeX, cubeZ); - ((IColumnInternal) lastChunk).setColumn(false); - - lastChunkView = new ChunkBlockView(lastChunk); - - removeBedrock(lastChunkView, rand); + return Pair.of(chunk, new SafeMutableBlockView(Box.horizontalChunkSlice(0, 256), new ChunkBlockView(chunk))); } private static Int2ObjectFunction wrapBlockArray(Block[] compatBlocks) { @@ -393,127 +382,154 @@ public int size() { }; } - private void removeBedrock(IMutableBlockView chunk, Random rand) { - FillerInfo bottom = analyzeBottomFiller(chunk); - FillerInfo top = analyzeTopFiller(chunk); + @Override + public void populate(Cube cube) { + ICubeLoader loader = getCubeLoader(); + + int cx = cube.getX(); + int cy = cube.getY(); + int cz = cube.getZ(); + + try { + WorldgenHangWatchdog.startWorldGen(); - for (Vector3ic v : BOTTOM_BEDROCK_LAYER) { - if (chunk.getBlock(v.x(), v.y(), v.z()) == Blocks.bedrock) { - boolean isBottomLayer = v.y() == 0; + // Generate all relevant cubes and store them in an array cache + loader.cacheCubes(getCubesToGenerate(cx, cy, cz), Requirement.GENERATE); - // Remove 1 in 3 blocks of bedrock to create holes - // Increase to 1 in 2 for the bottom layer because it's too dense - if (rand.nextInt(isBottomLayer ? 2 : 3) == 0) { - chunk.setBlock(v.x(), v.y(), v.z(), bottom.filler); + if (cy >= 0 && cy < 16) { + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + ((IColumnInternal) loader.getColumn(cx + x, cz + z, Requirement.GENERATE)).recalculateStagingHeightmap(); + } } - } - } - for (Vector3ic v : topBedrockLayer) { - if (chunk.getBlock(v.x(), v.y(), v.z()) == Blocks.bedrock) { - boolean isTopLayer = v.y() == worldHeightBlocks - 1; + for (int dx = -1; dx <= 0; dx++) { + for (int dz = -1; dz <= 0; dz++) { + // For some bizarre reason, MC offsets its block positions by +8 when populating. This means that when a chunk + // gets populated, it's actually populating the 16x16 blocks centered on the +x/+z corner. This is how every MC + // populator works for some reason (who decided this???). As a result, we need to generate the 3 columns in the + // negative directions (-x,z, x,-z, -x,-z). - // Remove 1 in 3 blocks of bedrock to create holes - // Increase to 1 in 2 for the top layer because it's too dense - if (rand.nextInt(isTopLayer ? 2 : 3) == 0) { - chunk.setBlock(v.x(), v.y(), v.z(), top.filler); + populateChunk(loader, cx + dx, cz + dz); + } } } - } - } - @Override - public void populate(Cube cube) { - try { - getCubeLoader().pauseLoadCalls(); + for (Vector3ic v : getCubesToPopulate(cx, cy, cz)) { + Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); - WorldgenHangWatchdog.startWorldGen(); + decorator.populate(world, center); - try { - CubeGeneratorsRegistry.populateVanillaCube(world, cube.getCoords()); - } catch (Throwable t) { - CubicChunks.LOGGER.error( - "Could not run non-vanilla population for cube {},{},{}", - cube.getX(), - cube.getY(), - cube.getZ(), - t); + center.markPopulated(Cube.POP_000); } - Cube withinVanillaChunk = cube; + for (Vector3ic v : getFullyPopulatedCubes(cx, cy, cz)) { + Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); - // Cubes outside this range are only filled with their respective block - // No population takes place - if (!isWithinVanillaWorld(cube)) { - withinVanillaChunk = getCubeLoader() - .getCube(cube.getX(), 0, cube.getZ(), ICubeProviderServer.Requirement.GENERATE); + center.markPopulated(Cube.POP_ALL); } - // Populate the vanilla chunk if it isn't already - if (withinVanillaChunk != null && !withinVanillaChunk.isFullyPopulated()) - populateChunk(getCubeLoader(), cube); + for (Vector3ic v : getCubesToGenerate(cx, cy, cz)) { + loader.onCubeGenerated(v.x(), v.y(), v.z()); + } - // Always set the requested cube to populated, even if it's outside of the vanilla chunk (and therefore had - // no work done on it). - cube.setPopulated(true); - cube.setFullyPopulated(true); + if (!cube.isFullyPopulated()) { + for (Vector3ic v : AFFECTED_CUBES) { + Cube adj = loader.getCube(cx + v.x(), cy + v.y(), cz + v.z(), Requirement.GENERATE); + + if (!adj.isPopulated()) { + CubicChunks.LOGGER.error( + "Failed to populate cube {},{},{}: requires cube: {},{},{}", + cx, + cy, + cz, + cx + v.x(), + cy + v.y(), + cz + v.z()); + } + } + } + } catch (Throwable t) { + CubicChunks.LOGGER.error( + "Could not run non-vanilla population for cube {},{},{}", + cx, + cy, + cz, + t); } finally { WorldgenHangWatchdog.endWorldGen(); - - getCubeLoader().unpauseLoadCalls(); + loader.uncacheCubes(); } } - private boolean isWithinVanillaWorld(Cube cube) { - return cube.getY() >= 0 && cube.getY() < worldHeightCubes; + private static final Vector3ic[] AFFECTED_CUBES = { + new Vector3i(1, 0, 0), + new Vector3i(0, 1, 0), + new Vector3i(1, 1, 0), + new Vector3i(0, 0, 1), + new Vector3i(1, 0, 1), + new Vector3i(0, 1, 1), + new Vector3i(1, 1, 1), + }; + + private static final short[] CUBE_FLAGS = { + Cube.POP_100, + Cube.POP_010, + Cube.POP_110, + Cube.POP_001, + Cube.POP_101, + Cube.POP_011, + Cube.POP_111, + }; + + private Box getCubesToGenerate(int x, int y, int z) { + if (y >= 0 && y < 16) { + return new Box(x - 1, -1, x - 1, x + 1, 16, z + 1); + } else { + return new Box(x - 1, y - 1, x - 1, x + 1, y + 1, z + 1); + } } - private void populateChunk(ICubeLoader loader, Cube cube) { - // First we have to generate all surrounding cubes - for (int x = -1; x <= 1; x++) { - for (int z = -1; z <= 1; z++) { - for (int y = 0; y < 16; y++) { - loader.getCube(cube.getX() + x, y, cube.getZ() + z, ICubeProviderServer.Requirement.GENERATE); - } - } + private Box getCubesToPopulate(int x, int y, int z) { + if (y >= 0 && y < 16) { + return new Box(x - 1, -1, x - 1, x, 15, z); + } else { + return new Box(x - 1, y - 1, x - 1, x, y, z); } + } - // Second, we regenerate the heightmap of all horizontally adjacent cubes - for (int x = -1; x <= 1; x++) { - for (int z = -1; z <= 1; z++) { - Cube cube2 = loader - .getCube(cube.getX() + x, cube.getY(), cube.getZ() + z, ICubeProviderServer.Requirement.GENERATE); - ((IColumnInternal) cube2.getColumn()).recalculateStagingHeightmap(); - } + private Box getFullyPopulatedCubes(int x, int y, int z) { + if (y >= 0 && y < 16) { + return new Box(x, 0, z, x, 15, z); + } else { + return new Box(x, y, z, x, y, z); } + } - // Third, we mark the cubes in the current vanilla chunk as populated - for (int y = 0; y < worldHeightCubes; y++) { - Cube inColumn = loader.getCube(cube.getX(), y, cube.getZ(), ICubeProviderServer.Requirement.GENERATE); + private void populateChunk(ICubeLoader loader, int columnX, int columnZ) { + Chunk column = loader.getColumn(columnX, columnZ, Requirement.GENERATE); - inColumn.setPopulated(true); - inColumn.setFullyPopulated(true); - } + if (column.isTerrainPopulated) return; + + column.isTerrainPopulated = true; + column.isModified = true; try { CompatHandler.beforePopulate(world, vanilla); - // Then we can populate this cube - vanilla.populate(vanilla, cube.getX(), cube.getZ()); - - GameRegistry.generateWorld(cube.getX(), cube.getZ(), world, vanilla, world.getChunkProvider()); + vanilla.populate(vanilla, columnX, columnZ); - applyModGenerators(cube.getX(), cube.getZ(), world, vanilla, world.getChunkProvider()); + applyModGenerators(columnX, columnZ, world, vanilla, world.getChunkProvider()); } catch (Throwable t) { - CubicChunks.LOGGER.error("Could not populate cube {},{},{}", cube.getX(), cube.getY(), cube.getZ(), t); + CubicChunks.LOGGER.error("Could not populate column {},{}", columnX, columnZ, t); } finally { CompatHandler.afterPopulate(world); } } - // First proider is the ChunkProviderGenerate/Hell/End/Flat second is the serverChunkProvider + // First provider is the ChunkProviderGenerate/Hell/End/Flat second is the serverChunkProvider private void applyModGenerators(int x, int z, World world, IChunkProvider vanillaGen, IChunkProvider provider) { - List generators = IGameRegistry.getSortedGeneratorList(); + List generators = IGameRegistry.getSortedGeneratorList(); if (generators == null) { IGameRegistry.computeGenerators(); generators = IGameRegistry.getSortedGeneratorList(); @@ -525,7 +541,7 @@ private void applyModGenerators(int x, int z, World world, IChunkProvider vanill long zSeed = fmlRandom.nextLong() >> 2 + 1L; long chunkSeed = (xSeed * x + zSeed * z) ^ worldSeed; - for (IWorldGenerator generator : generators) { + for (cpw.mods.fml.common.IWorldGenerator generator : generators) { fmlRandom.setSeed(chunkSeed); try { CompatHandler.beforeGenerate(world, generator); @@ -536,6 +552,24 @@ private void applyModGenerators(int x, int z, World world, IChunkProvider vanill } } + @Override + public void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel wanted) { + boolean generate = actual.ordinal() < CubeInitLevel.Generated.ordinal() && wanted.ordinal() >= CubeInitLevel.Generated.ordinal(); + boolean populate = actual.ordinal() < CubeInitLevel.Populated.ordinal() && wanted.ordinal() >= CubeInitLevel.Populated.ordinal(); + + if (generate) { + if (vanilla instanceof Precalculable precalc) { + precalc.precalculate(pos.getX(), pos.getY(), pos.getZ()); + } + + decorator.pregenerate(world, pos); + } + + if (populate) { + decorator.prepopulate(world, pos); + } + } + @Override public void recreateStructures(ICube cube) {} diff --git a/src/main/resources/assets/cubicchunks/lang/en_US.lang b/src/main/resources/assets/cubicchunks/lang/en_US.lang index 4786ef09..52306d4f 100644 --- a/src/main/resources/assets/cubicchunks/lang/en_US.lang +++ b/src/main/resources/assets/cubicchunks/lang/en_US.lang @@ -25,3 +25,8 @@ cubicchunks.command.config.set.done=Config option %s has been set to "%s" cubicchunks.command.config.set.requires_restart=Changing config option %s requires world restart! cubicchunks.command.usage.config.set.primitive=Config option %s has type %s and requires 1 parameter cubicchunks.command.config.reload.done=Cubic Chunks config has been reloaded + +cubicchunks.config.optimizations=Optimizations +cubicchunks.config.optimizations.background_threads=Background Threads + +generator.VanillaCubic=Vanilla + Cubic From 64484768c1a814a71f292de774f2cbdda4b1f76b Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 14 Nov 2025 12:03:24 -0500 Subject: [PATCH 07/22] spotless --- .../cardinalstar/cubicchunks/CubicChunks.java | 5 +- .../cubicchunks/CubicChunksConfig.java | 12 +-- .../cardinalstar/cubicchunks/api/XYZMap.java | 1 + .../api/registry/IRegistryEntry.java | 1 + .../world/storage/StorageFormatFactory.java | 1 + .../api/worldgen/DependencyRegistry.java | 3 + .../api/worldgen/GenerationResult.java | 3 +- .../api/worldgen/IWorldGenerator.java | 2 +- .../worldgen/decoration/ICubeGenerator.java | 3 +- .../worldgen/decoration/ICubePopulator.java | 1 - .../worldgen/decoration/IWorldDecorator.java | 7 +- .../worldgen/impl/StandardWorldDecorator.java | 2 +- .../api/worldtype/VanillaCubicWorldType.java | 5 +- .../cubicchunks/async/TaskPool.java | 51 ++++++------ .../cubicchunks/client/WorldDiagnostics.java | 1 + .../cubicchunks/event/events/CubeEvent.java | 1 - .../event/handlers/ClientEventHandler.java | 1 + .../cubicchunks/mixin/Mixins.java | 8 +- .../early/client/MixinEntityRenderer.java | 44 +++++----- .../common/MixinExtendedBlockStorage.java | 4 +- .../mixin/early/common/MixinWorld.java | 6 +- .../early/common/MixinWorldProvider.java | 3 +- .../mixin/early/common/MixinWorldServer.java | 11 ++- .../early/common/MixinWorld_HeightLimit.java | 7 +- .../worldgen/MixinChunkProviderGenerate.java | 10 ++- .../modcompat/angelica/IAngelicaDelegate.java | 2 + .../network/PacketEncoderColumn.java | 3 +- .../network/PacketEncoderCubeBlockChange.java | 1 + .../network/PacketEncoderCubes.java | 4 +- .../network/PacketEncoderUnloadColumn.java | 3 +- .../network/PacketEncoderUnloadCube.java | 3 +- .../server/CubeProviderServer.java | 7 +- .../cubicchunks/server/chunkio/CubeIO.java | 32 ++++---- .../server/chunkio/CubeLoaderServer.java | 6 +- .../cubicchunks/server/chunkio/ICubeIO.java | 4 + .../server/chunkio/ICubeLoader.java | 12 ++- .../server/chunkio/IONbtReader.java | 2 +- .../server/chunkio/IONbtWriter.java | 1 + .../chunkio/IPreloadFailureDelegate.java | 1 + .../server/chunkio/RegionCubeStorage.java | 24 ++++-- .../chunkio/region/ShadowPagingRegion.java | 3 +- .../cardinalstar/cubicchunks/util/Coords.java | 3 +- .../cubicchunks/util/DependencyGraph.java | 24 ++++-- .../world/savedata/WorldFormatSavedData.java | 9 ++- .../world/worldgen/FeatureCubicGenerator.java | 18 +++-- .../world/worldgen/MapGenCaveFluids.java | 14 ++-- .../world/worldgen/MapGenCavesCubic.java | 13 ++- .../world/worldgen/WorldGenerators.java | 43 +++++----- .../world/worldgen/WorldgenFeature.java | 19 ++--- .../compat/DeepslateCubePopulator.java | 1 + .../world/worldgen/data/FastCubePosMap.java | 1 + .../worldgen/data/NoisePrecalculator.java | 6 +- .../worldgen/modern/NoodleCaveGenerator.java | 3 + .../modern/SpaghettiCaveGenerator.java | 2 + .../worldgen/noise/BlockNoiseSampler.java | 1 + .../world/worldgen/noise/NoiseSampler.java | 1 + .../worldgen/noise/SimplexNoiseSampler.java | 4 +- .../vanilla/PrecalcedVanillaOctaves.java | 27 +++++-- .../worldgen/VanillaWorldGenerator.java | 80 +++++++++---------- 59 files changed, 346 insertions(+), 224 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index d284d0d9..c9c3066a 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -54,6 +54,7 @@ import com.cardinalstar.cubicchunks.worldgen.WorldgenHangWatchdog; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; + import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; @@ -196,9 +197,7 @@ public void onServerAboutToStart(FMLServerAboutToStartEvent event) { } public static void registerAnvil3dStorageFormatProvider() { - StorageFormatFactory.REGISTRY.register( - StorageFormatFactory.DEFAULT, - new DefaultStorageFormatFactory()); + StorageFormatFactory.REGISTRY.register(StorageFormatFactory.DEFAULT, new DefaultStorageFormatFactory()); } @NetworkCheckHandler diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java index 163150ad..edb2c7ba 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java @@ -52,6 +52,7 @@ import com.google.common.collect.TreeRangeSet; import com.gtnewhorizon.gtnhlib.config.Config; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; + import cpw.mods.fml.client.event.ConfigChangedEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -71,10 +72,10 @@ public class CubicChunksConfig { @Config.LangKey("cubicchunks.config.force_cc") @Config.Comment(""" - Determines when a cubic chunks world should be created for non-cubic-chunks world types. - DEFAULT - only when cubic chunks world type - LOAD_NOT_EXCLUDED - load all worlds as cubic chunks, except excluded dimensions - ALWAYS - load everything as cubic chunks. Overrides forceDimensionExcludes""") + Determines when a cubic chunks world should be created for non-cubic-chunks world types. + DEFAULT - only when cubic chunks world type + LOAD_NOT_EXCLUDED - load all worlds as cubic chunks, except excluded dimensions + ALWAYS - load everything as cubic chunks. Overrides forceDimensionExcludes""") public static ForceCCMode forceLoadCubicChunks = ForceCCMode.DEFAULT; @Config.LangKey("cubicchunks.config.cubegen_per_tick") @@ -231,7 +232,8 @@ public static final class Optimizations { @Config.LangKey("cubicchunks.config.optimizations.background_threads") @Config.Comment("Maximum number of threads to use for background tasks (world I/O, noise generation, etc).") - public int backgroundThreads = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors() / 2; + public int backgroundThreads = ManagementFactory.getOperatingSystemMXBean() + .getAvailableProcessors() / 2; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java index 5e33db83..80216b97 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java @@ -26,6 +26,7 @@ import javax.annotation.ParametersAreNonnullByDefault; import com.cardinalstar.cubicchunks.util.Coords; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; /** diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java index f4ef97bc..c94fe801 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java @@ -18,5 +18,6 @@ public interface IRegistryEntry { UniqueIdentifier getRegistryName(); String getLocalizedName(); + String getUnlocalizedName(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java index dc442e9f..5b05a8e5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/StorageFormatFactory.java @@ -29,6 +29,7 @@ import com.cardinalstar.cubicchunks.api.registry.AbstractRegistryEntry; import com.cardinalstar.cubicchunks.api.registry.Registry; + import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; public abstract class StorageFormatFactory extends AbstractRegistryEntry { diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java index 92c464dd..6589df1c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java @@ -5,8 +5,11 @@ public interface DependencyRegistry { void register(String name, T value, String... deps); + void registerTarget(String name, String... deps); + void addDependency(String from, String to); + void removeDependency(String from, String to); List sorted(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java index 83a25ef4..16de93f6 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java @@ -21,7 +21,8 @@ public GenerationResult(T object) { public GenerationResult(T object, List columnSideEffects, List cubeSideEffects) { this.object = object; - this.columnSideEffects = columnSideEffects == null ? ImmutableList.of() : ImmutableList.copyOf(columnSideEffects); + this.columnSideEffects = columnSideEffects == null ? ImmutableList.of() + : ImmutableList.copyOf(columnSideEffects); this.cubeSideEffects = cubeSideEffects == null ? ImmutableList.of() : ImmutableList.copyOf(cubeSideEffects); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java index 8bd8e610..1603156a 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java @@ -105,7 +105,7 @@ public interface IWorldGenerator { * Gets the closest structure with name {@code name}. This is primarily used when an eye of ender is trying to find * a stronghold. * - * @param name the name of the structure + * @param name the name of the structure * * @return the position of the structure, or {@code null} if none could be found */ diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java index af793230..a354f052 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java @@ -5,7 +5,8 @@ import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; -/// Generates the terrain for a cube. The cube is not in the world during generation and block operations must not be performed. +/// Generates the terrain for a cube. The cube is not in the world during generation and block operations must not be +/// performed. public interface ICubeGenerator { /// Hints to any off-thread precachers to submit a generation request for a cube that will be generated in the diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java index c8bb3fbb..b64467dc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubePopulator.java @@ -22,7 +22,6 @@ import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.worldgen.BuiltinWorldDecorators; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java index 1a640a41..4357175d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java @@ -2,7 +2,6 @@ import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.worldgen.impl.StandardWorldDecorator; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; @@ -11,7 +10,8 @@ public interface IWorldDecorator { /// Runs prior to [#generateCube(World, Cube)], when a cube is in the eager-loading queue and needs to be generated. - /// There is no guarantee pregenerated cubes will be eventually loaded. Cubes can be removed from the eager loading queue. + /// There is no guarantee pregenerated cubes will be eventually loaded. Cubes can be removed from the eager loading + /// queue. void pregenerate(World world, CubePos pos); /// Runs terrain generation on the cube. The cube is not in the world at this point and block operations should not @@ -24,6 +24,7 @@ public interface IWorldDecorator { void prepopulate(World world, CubePos pos); /// Populates the cube. The cube is in the world at this point, but be careful of causing cascading worldgen when - /// accessing blocks outside the current cube. Note that by convention MC populates the 16x16x16 box around the +X,+Y,+Z corner, not whole cube. + /// accessing blocks outside the current cube. Note that by convention MC populates the 16x16x16 box around the + /// +X,+Y,+Z corner, not whole cube. void populate(World world, Cube cube); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java index 91ba0645..b5e5178f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java @@ -3,10 +3,10 @@ import net.minecraft.world.World; import com.cardinalstar.cubicchunks.api.worldgen.DependencyRegistry; -import com.cardinalstar.cubicchunks.api.worldgen.decoration.IWorldDecorator; import com.cardinalstar.cubicchunks.api.worldgen.WorldgenRegistry; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; +import com.cardinalstar.cubicchunks.api.worldgen.decoration.IWorldDecorator; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java index 1567e6a7..5dcb702e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldtype/VanillaCubicWorldType.java @@ -52,7 +52,10 @@ public static void init() { @Override public @NotNull IWorldGenerator createCubeGenerator(World world) { - return new VanillaWorldGenerator(world.provider.createChunkGenerator(), world, BuiltinWorldDecorators.CUBIC_VANILLA); + return new VanillaWorldGenerator( + world.provider.createChunkGenerator(), + world, + BuiltinWorldDecorators.CUBIC_VANILLA); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java index babdec17..298cab16 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -1,6 +1,5 @@ package com.cardinalstar.cubicchunks.async; -import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; @@ -37,15 +36,17 @@ public class TaskPool { private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor( 1, CubicChunksConfig.optimizations.backgroundThreads, - 60, TimeUnit.SECONDS, + 60, + TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024), THREAD_FACTORY, new DiscardPolicy()); - private static final ScheduledExecutorService SCHEDULED = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY); + private static final ScheduledExecutorService SCHEDULED = Executors + .newSingleThreadScheduledExecutor(THREAD_FACTORY); // static { - // SCHEDULED.scheduleAtFixedRate(TaskPool::flushLastTask, 1, 1, TimeUnit.MILLISECONDS); + // SCHEDULED.scheduleAtFixedRate(TaskPool::flushLastTask, 1, 1, TimeUnit.MILLISECONDS); // } // private static final Object lock = new Object(); @@ -61,33 +62,33 @@ public static Future submit(ITask task, @Nullable Consumer callback long now = System.nanoTime(); synchronized (lock) { -// if (lastTask != null) { -// Task merged; -// -// if (lastTask.submitTime + TASK_BATCHING_PERIOD < now) { -// POOL.submit(lastTask); -// lastTask = null; -// } else if ((merged = lastTask.tryMerge(task, now, callback)) != null) { -// return merged; -// } -// } + // if (lastTask != null) { + // Task merged; + // + // if (lastTask.submitTime + TASK_BATCHING_PERIOD < now) { + // POOL.submit(lastTask); + // lastTask = null; + // } else if ((merged = lastTask.tryMerge(task, now, callback)) != null) { + // return merged; + // } + // } Task future = new Task<>(task, now, callback); -// lastTask = future; + // lastTask = future; POOL.submit(future); return future; } } // private static void flushLastTask() { - // synchronized (lock) { - // if (lastTask != null) { - // if (lastTask.submitTime + TASK_BATCHING_PERIOD < System.nanoTime()) { - // POOL.submit(lastTask); - // lastTask = null; - // } - // } - // } + // synchronized (lock) { + // if (lastTask != null) { + // if (lastTask.submitTime + TASK_BATCHING_PERIOD < System.nanoTime()) { + // POOL.submit(lastTask); + // lastTask = null; + // } + // } + // } // } public interface ITask extends Callable { @@ -106,7 +107,9 @@ default T callMerged(List> mergedTasks) throws Exception { public interface ITaskFuture { ITask getTask(); + void finish(T value); + void fail(Exception ex); } @@ -145,7 +148,7 @@ public synchronized Task tryMerge(ITask other, long now, @Nullable public synchronized void run() { try { if (merged != null) { - //noinspection unchecked + // noinspection unchecked finish(task.callMerged((List>) (List) merged)); } else { finish(task.call()); diff --git a/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java b/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java index 345038e5..e0fb70bc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java +++ b/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java @@ -8,6 +8,7 @@ import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; + import cpw.mods.fml.common.eventhandler.SubscribeEvent; @EventBusSubscriber diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java b/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java index c8aca66b..101f3e4c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/events/CubeEvent.java @@ -75,7 +75,6 @@ public LoadNBT(World world, CubePos pos, NBTTagCompound tag) { } } - public static class SaveNBT extends CubeEvent { public NBTTagCompound tag; diff --git a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java index 35cc3e46..88ae8d62 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java @@ -40,6 +40,7 @@ import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.server.ICubicPlayerList; import com.cardinalstar.cubicchunks.util.MathUtil; + import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index a708258c..0ec8ff88 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -170,10 +170,10 @@ public enum Mixins implements IMixins { new MixinBuilder("Fixing mobs walking off into chasms below y = 0").addCommonMixins("common.MixinPathFinder") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), - MIXIN_ENTITY_BRIGHTNESS(new MixinBuilder("Fix Entity.getBrightness") - .addCommonMixins("common.MixinEntity_Brightness") - .setPhase(Phase.EARLY) - .setApplyIf(() -> true)), + MIXIN_ENTITY_BRIGHTNESS( + new MixinBuilder("Fix Entity.getBrightness").addCommonMixins("common.MixinEntity_Brightness") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), // SERVER MIXIN_INTEGRATED_SERVER_ACCESSOR( diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java index bdd8634c..ff4bafd4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java @@ -87,27 +87,27 @@ public float modifyVoidFog(EntityRenderer instance, Operation original, return 1000f; // Temporarily disable this - it breaks angelica -// float farPlaneDistance = original.call(instance); -// -// if (!this.mc.theWorld.provider.getWorldHasVoidParticles()) { -// return farPlaneDistance; -// } -// -// EntityLivingBase player = this.mc.renderViewEntity; -// -// int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; -// double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double) partialTicks; -// -// double fogStrength = skylight / 16.0D + Math.max(playerY / 32.0D, 0.25); -// -// if (fogStrength < 1.0D) { -// if (fogStrength < 0.0D) { -// fogStrength = 0.0D; -// } -// -// farPlaneDistance *= (float) (fogStrength * fogStrength); -// } -// -// return farPlaneDistance; + // float farPlaneDistance = original.call(instance); + // + // if (!this.mc.theWorld.provider.getWorldHasVoidParticles()) { + // return farPlaneDistance; + // } + // + // EntityLivingBase player = this.mc.renderViewEntity; + // + // int skylight = (player.getBrightnessForRender(partialTicks) & 0xf00000) >> 20; + // double playerY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double) partialTicks; + // + // double fogStrength = skylight / 16.0D + Math.max(playerY / 32.0D, 0.25); + // + // if (fogStrength < 1.0D) { + // if (fogStrength < 0.0D) { + // fogStrength = 0.0D; + // } + // + // farPlaneDistance *= (float) (fogStrength * fogStrength); + // } + // + // return farPlaneDistance; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java index f17ef579..9bf3eb5a 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java @@ -17,7 +17,9 @@ public class MixinExtendedBlockStorage { @Unique private int prevId = 0; - @Redirect(method = "getBlockByExtId", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;getBlockById(I)Lnet/minecraft/block/Block;")) + @Redirect( + method = "getBlockByExtId", + at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;getBlockById(I)Lnet/minecraft/block/Block;")) public Block optimizeGetBlock(int id) { if (id == prevId) return prevBlock; diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java index bbdadc11..d484c684 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java @@ -216,7 +216,9 @@ public abstract class MixinWorld implements ICubicWorldInternal { @Redirect( method = "(Lnet/minecraft/world/storage/ISaveHandler;Ljava/lang/String;Lnet/minecraft/world/WorldSettings;Lnet/minecraft/world/WorldProvider;Lnet/minecraft/profiler/Profiler;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;createChunkProvider()Lnet/minecraft/world/chunk/IChunkProvider;")) + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/World;createChunkProvider()Lnet/minecraft/world/chunk/IChunkProvider;")) public IChunkProvider noopCreateProvider(World instance) { // Done below manually return null; @@ -229,7 +231,7 @@ public void initWorld(ISaveHandler p_i45369_1_, String p_i45369_2_, WorldSetting WorldProvider p_i45369_4_, Profiler p_i45369_5_, CallbackInfo ci) { // Some other world instantiation that we don't care about (fake dummy worlds, for instance) - //noinspection ConstantValue + // noinspection ConstantValue if (!((Object) this instanceof WorldServer worldServer)) return; ((ICubicWorldInternal.Server) this).initCubicWorldServer(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java index 9095e9e4..02994d2f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java @@ -96,7 +96,8 @@ public int getOriginalActualHeight() { @Nullable @Override public IWorldGenerator createCubeGenerator() { - WorldType terrainType = worldObj.getWorldInfo().getTerrainType(); + WorldType terrainType = worldObj.getWorldInfo() + .getTerrainType(); if (terrainType instanceof ICubicWorldType ccWorldType && ccWorldType.hasCubicGeneratorForWorld(worldObj)) { return ccWorldType.createCubeGenerator(worldObj); diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java index 570b1705..bbc979b3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java @@ -172,8 +172,11 @@ private void redirectChunkSaveLocation(CallbackInfoReturnable cir) { if (this.theChunkProviderServer == null) { WorldFormatSavedData format = WorldFormatSavedData.get((WorldServer) (Object) this); - //noinspection DataFlowIssue - cir.setReturnValue(format.getFormat().getWorldSaveDirectory(this.saveHandler, (WorldServer) (Object) this).toFile()); + // noinspection DataFlowIssue + cir.setReturnValue( + format.getFormat() + .getWorldSaveDirectory(this.saveHandler, (WorldServer) (Object) this) + .toFile()); } } @@ -439,7 +442,9 @@ private void tickColumn(boolean raining, boolean thundering, Chunk chunk) { /// Immediate block updates rarely work well in CC. Vanilla expects there to be a hard limit to the number of steps /// something can take, but CC removes many of those limits so it's better to just disable the feature for now. - @Redirect(method = "scheduleBlockUpdateWithPriority", at = @At(value = "FIELD", target = "Lnet/minecraft/world/WorldServer;scheduledUpdatesAreImmediate:Z")) + @Redirect( + method = "scheduleBlockUpdateWithPriority", + at = @At(value = "FIELD", target = "Lnet/minecraft/world/WorldServer;scheduledUpdatesAreImmediate:Z")) private boolean disableImmediateBlockUpdates(WorldServer instance) { return false; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java index e961eda2..dd87d191 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld_HeightLimit.java @@ -533,8 +533,11 @@ private boolean isValidForRendering(Chunk column, @Local(argsOnly = true, ordina return cube.isPopulated(); } - @Redirect(method = "getBiomeGenForCoordsBody", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;blockExists(III)Z")) + @Redirect( + method = "getBiomeGenForCoordsBody", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;blockExists(III)Z")) private boolean checkColumnExists(World instance, int x, int zero, int z) { - return ((ICubicWorld) instance).getCubeCache().getLoadedColumn(x >> 4, z >> 4) != null; + return ((ICubicWorld) instance).getCubeCache() + .getLoadedColumn(x >> 4, z >> 4) != null; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java index c903ca60..2d708e5d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java @@ -18,6 +18,7 @@ import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalculableNoise; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; + import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -36,10 +37,11 @@ public class MixinChunkProviderGenerate implements Precalculable { @Shadow public NoiseGeneratorOctaves noiseGen6; -// @Redirect(method = "", at = @At(value = "NEW", target = "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) -// public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves) { -// return new PrecalcedVanillaOctaves(random, octaves); -// } + // @Redirect(method = "", at = @At(value = "NEW", target = + // "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) + // public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves) { + // return new PrecalcedVanillaOctaves(random, octaves); + // } @Definition( id = "caveGenerator", diff --git a/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java index 9a6ed2ea..44d85ce0 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java @@ -3,9 +3,11 @@ public interface IAngelicaDelegate { void onColumnLoaded(int chunkX, int chunkZ); + void onColumnUnloaded(int chunkX, int chunkZ); void onCubeLoaded(int cubeX, int cubeY, int cubeZ); + void onCubeUnloaded(int cubeX, int cubeY, int cubeZ); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java index aa8fbf9c..b9ea141f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java @@ -86,7 +86,8 @@ public void process(World world, PacketColumn packet) { }); if (AngelicaInterop.hasDelegate()) { - AngelicaInterop.getDelegate().onColumnLoaded(packet.chunkX, packet.chunkZ); + AngelicaInterop.getDelegate() + .onColumnLoaded(packet.chunkX, packet.chunkZ); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index ba87eb91..0e3cc4cf 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -36,6 +36,7 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; + import gnu.trove.TShortCollection; import gnu.trove.iterator.TIntIterator; import gnu.trove.set.TIntSet; diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index 90c00721..03459d96 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -37,6 +37,7 @@ import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; + import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -149,7 +150,8 @@ public void process(World world, PacketCubes packet) { cube.markForRenderUpdate(); if (AngelicaInterop.hasDelegate()) { - AngelicaInterop.getDelegate().onCubeLoaded(cube.getX(), cube.getY(), cube.getZ()); + AngelicaInterop.getDelegate() + .onCubeLoaded(cube.getX(), cube.getY(), cube.getZ()); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java index fdfd333e..7067d9f9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java @@ -71,7 +71,8 @@ public void process(World world, PacketUnloadColumn packet) { cubeCache.unloadChunk(packet.chunkX, packet.chunkZ); if (AngelicaInterop.hasDelegate()) { - AngelicaInterop.getDelegate().onColumnUnloaded(packet.chunkX, packet.chunkZ); + AngelicaInterop.getDelegate() + .onColumnUnloaded(packet.chunkX, packet.chunkZ); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java index 57b6f0a9..2aac913e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadCube.java @@ -74,7 +74,8 @@ public void process(World world, PacketUnloadCube packet) { cubeCache.unloadCube(packet.pos); if (AngelicaInterop.hasDelegate()) { - AngelicaInterop.getDelegate().onCubeUnloaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); + AngelicaInterop.getDelegate() + .onCubeUnloaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index c1c9e1f5..b7c95f94 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -69,6 +69,7 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; + import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; /** @@ -139,7 +140,8 @@ public CubeProviderServer(WorldServer worldServer, IChunkLoader chunkLoader, IWo this.cubeLoader = new CubeLoaderServer( worldServer, - format.getFormat().provideStorage(worldServer, path), + format.getFormat() + .provideStorage(worldServer, path), worldGenerator, new LoadingCallbacks()); } catch (IOException e) { @@ -336,8 +338,7 @@ private void doEagerLoading() { cubeLoader.unpauseLoadCalls(); - CubeInitLevel actual = cube == null ? CubeInitLevel.None - : cube.getInitLevel(); + CubeInitLevel actual = cube == null ? CubeInitLevel.None : cube.getInitLevel(); CubeInitLevel wanted = CubeInitLevel.fromRequirement(request.effort); if (actual.ordinal() < wanted.ordinal()) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index d8b84649..ce30a550 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -13,7 +13,6 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.storage.IThreadedFileIO; import net.minecraft.world.storage.ThreadedFileIOBase; - import net.minecraftforge.common.MinecraftForge; import com.cardinalstar.cubicchunks.CubicChunks; @@ -25,6 +24,7 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; + import it.unimi.dsi.fastutil.Pair; public class CubeIO implements ICubeIO, IThreadedFileIO { @@ -39,20 +39,20 @@ public class CubeIO implements ICubeIO, IThreadedFileIO { private final Map pendingCubes = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") - private final Cache columnCache = ((CacheBuilder) (Object) CacheBuilder.newBuilder()) - .expireAfterAccess(60, TimeUnit.SECONDS) - .softValues() - .initialCapacity(512) - .maximumSize(4096) - .build(); + private final Cache columnCache = ((CacheBuilder) (Object) CacheBuilder + .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) + .softValues() + .initialCapacity(512) + .maximumSize(4096) + .build(); @SuppressWarnings("unchecked") - private final Cache cubeCache = ((CacheBuilder) (Object) CacheBuilder.newBuilder()) - .expireAfterAccess(60, TimeUnit.SECONDS) - .softValues() - .initialCapacity(1024) - .maximumSize(8192) - .build(); + private final Cache cubeCache = ((CacheBuilder) (Object) CacheBuilder + .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) + .softValues() + .initialCapacity(1024) + .maximumSize(8192) + .build(); public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { this.storage = storage; @@ -62,7 +62,8 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { @Override public boolean columnExists(ChunkCoordIntPair pos) { if (pendingColumns.containsKey(pos)) return true; - if (columnCache.asMap().containsKey(pos)) return true; + if (columnCache.asMap() + .containsKey(pos)) return true; try { if (storage.columnExists(pos)) return true; @@ -76,7 +77,8 @@ public boolean columnExists(ChunkCoordIntPair pos) { @Override public boolean cubeExists(CubePos pos) { if (pendingCubes.containsKey(pos)) return true; - if (cubeCache.asMap().containsKey(pos)) return true; + if (cubeCache.asMap() + .containsKey(pos)) return true; try { if (storage.cubeExists(pos)) return true; diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index 578c34fb..4f2fd26c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -30,6 +30,7 @@ import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; + import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; public class CubeLoaderServer implements ICubeLoader { @@ -161,7 +162,10 @@ public Cube getCube(int x, int y, int z, Requirement effort) { if (cache != null) { Cube cube = cache.get(x, y, z); - if (cube != null && cube.getInitLevel().ordinal() >= CubeInitLevel.fromRequirement(effort).ordinal()) { + if (cube != null && cube.getInitLevel() + .ordinal() + >= CubeInitLevel.fromRequirement(effort) + .ordinal()) { return cube; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java index 9a85290a..225a14a2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java @@ -13,14 +13,18 @@ public interface ICubeIO extends Flushable, Closeable { boolean columnExists(ChunkCoordIntPair pos); + boolean cubeExists(CubePos pos); void saveColumn(ChunkCoordIntPair pos, Chunk column); + void saveCube(CubePos pos, Cube cube); NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureException; + NBTTagCompound loadCube(CubePos pos) throws LoadFailureException; void preloadColumn(ChunkCoordIntPair pos); + void preloadCube(CubePos pos, CubeInitLevel level); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java index e0700fc2..73006dc4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java @@ -25,18 +25,24 @@ default boolean columnExists(int x, int z) { boolean cubeExists(int x, int y, int z); - Cube getCube(int x, int y, int z, Requirement effort); + /// Notifies this loader that a cube was generated further, either by a populator or something else. void onCubeGenerated(int x, int y, int z); void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz, Requirement effort); + default void cacheCubes(Box box, Requirement effort) { cacheCubes( - box.getX1(), box.getY1(), box.getZ1(), - box.getX2() - box.getX1(), box.getY2() - box.getY1(), box.getZ2() - box.getZ1(), + box.getX1(), + box.getY1(), + box.getZ1(), + box.getX2() - box.getX1(), + box.getY2() - box.getY1(), + box.getZ2() - box.getZ1(), effort); } + void uncacheCubes(); void unloadCube(int x, int y, int z); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java index 21d9f875..3acdf73b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java @@ -64,7 +64,7 @@ static Chunk readColumn(World world, int x, int z, NBTTagCompound nbt) { readOpacityIndex(level, column); column.isModified = false; // its exactly the same as on disk so its not modified - ((IColumnInternal)column).setColumn(true); + ((IColumnInternal) column).setColumn(true); return column; // TODO: use Chunk, not IColumn, whenever possible } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java index ca46bb6c..be39c14e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java @@ -52,6 +52,7 @@ import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; + import cpw.mods.fml.common.FMLLog; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java index 0e3b675c..2f75e6d8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java @@ -9,6 +9,7 @@ public interface IPreloadFailureDelegate { void onColumnPreloadFailed(ChunkCoordIntPair pos); + void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel wanted); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java index c0f2e8e3..67bbc252 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java @@ -42,6 +42,7 @@ import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; import com.cardinalstar.cubicchunks.server.chunkio.region.ShadowPagingRegion; import com.cardinalstar.cubicchunks.util.CubePos; + import cubicchunks.regionlib.impl.EntryLocation2D; import cubicchunks.regionlib.impl.EntryLocation3D; import cubicchunks.regionlib.impl.SaveCubeColumns; @@ -82,13 +83,20 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName())))), + (dir, key) -> Files.exists( + dir.resolve( + key.getRegionKey() + .getName())))), new SharedCachedRegionProvider<>( new SimpleRegionProvider<>( new EntryLocation2D.Provider(), part2d, - (keyProvider, regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName() + ".ext"))))); + (keyProvider, + regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), + (dir, key) -> Files.exists( + dir.resolve( + key.getRegionKey() + .getName() + ".ext"))))); @SuppressWarnings("unchecked") SaveSection3D section3d = new SaveSection3D( new SharedCachedRegionProvider<>( @@ -101,14 +109,20 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName())))), + (dir, key) -> Files.exists( + dir.resolve( + key.getRegionKey() + .getName())))), new SharedCachedRegionProvider<>( new SimpleRegionProvider<>( new EntryLocation3D.Provider(), part3d, (keyProvider, regionKey) -> new ExtRegion<>(part3d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists(dir.resolve(key.getRegionKey().getName() + ".ext"))))); + (dir, key) -> Files.exists( + dir.resolve( + key.getRegionKey() + .getName() + ".ext"))))); return new SaveCubeColumns(section2d, section3d); } else { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java index 7a7b76f6..188b0663 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/region/ShadowPagingRegion.java @@ -223,7 +223,8 @@ private boolean writeDataPass(Map entries, List tempBuffers = new ArrayList<>(); ByteBuffer lengthPrefixBuffer = ByteBuffer.allocate(Integer.BYTES); - for (Iterator> itr = entries.entrySet().iterator(); itr.hasNext();) { + for (Iterator> itr = entries.entrySet() + .iterator(); itr.hasNext();) { Map.Entry entry = itr.next(); K key = entry.getKey(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java index 16503c85..cdbc54ed 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java @@ -163,7 +163,8 @@ public static Random coordsSeedRandom(long seed, int x, int y, int z) { private static long pack(long k, int shift) { if (k < ~MASK || k > MASK) { - throw new IllegalArgumentException("Can only pack numbers between " + (~MASK) + ".." + MASK + " (inclusive): got " + k); + throw new IllegalArgumentException( + "Can only pack numbers between " + (~MASK) + ".." + MASK + " (inclusive): got " + k); } // Trim off the upper bits, preserving the sign diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java index 54f067c5..51952e0d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -9,12 +9,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; + import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; public class DependencyGraph { private static class DepInfo { + public final String dependency; public final boolean optional; @@ -38,7 +40,9 @@ public int hashCode() { private final Object2ObjectOpenHashMap objects = new Object2ObjectOpenHashMap<>(); - private final Multimap dependencies = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap dependencies = MultimapBuilder.hashKeys() + .hashSetValues() + .build(); private List cachedSorted; @@ -64,10 +68,12 @@ public void addUnparsedDependency(String object, String dep) { } if (dep.startsWith(REQUIRES)) { - dep = dep.substring(REQUIRES.length()).trim(); + dep = dep.substring(REQUIRES.length()) + .trim(); dependencies.put(object, new DepInfo(dep, optional)); } else if (dep.startsWith(REQUIRED_BY)) { - dep = dep.substring(REQUIRED_BY.length()).trim(); + dep = dep.substring(REQUIRED_BY.length()) + .trim(); dependencies.put(dep, new DepInfo(object, optional)); } else { dependencies.put(object, new DepInfo(dep, optional)); @@ -114,8 +120,7 @@ public List sorted() { while (!remaining.isEmpty()) { Iterator iter = remaining.iterator(); - iterdeps: - while (iter.hasNext()) { + iterdeps: while (iter.hasNext()) { String curr = iter.next(); for (DepInfo dep : dependencies.get(curr)) { @@ -144,12 +149,15 @@ public List sorted() { private void preventCyclicDeps(String node, boolean optional, Set path) { if (path.contains(node)) { - throw new IllegalStateException(node - + " has a cyclic dependency with itself. The path is: " + path.stream().reduce("", (s, s2) -> s + ", " + s2)); + throw new IllegalStateException( + node + " has a cyclic dependency with itself. The path is: " + + path.stream() + .reduce("", (s, s2) -> s + ", " + s2)); } if (!optional && !objects.containsKey(node)) { - throw new IllegalStateException(node + " is present in the dependency graph but does not have a matching object"); + throw new IllegalStateException( + node + " is present in the dependency graph but does not have a matching object"); } path.add(node); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java b/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java index e65e3af5..5182e972 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java @@ -6,6 +6,7 @@ import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatFactory; + import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; public class WorldFormatSavedData extends WorldSavedData { @@ -25,7 +26,8 @@ public void readFromNBT(NBTTagCompound tag) { format = StorageFormatFactory.REGISTRY.get(new UniqueIdentifier(tag.getString("format"))); if (format == null) { - throw new IllegalStateException("Could not load world: save format was not registered: " + tag.getString("format")); + throw new IllegalStateException( + "Could not load world: save format was not registered: " + tag.getString("format")); } } @@ -35,9 +37,8 @@ public void writeToNBT(NBTTagCompound tag) { } public static WorldFormatSavedData get(World world) { - WorldFormatSavedData data = (WorldFormatSavedData) world.mapStorage.loadData( - WorldFormatSavedData.class, - "cubicchunks.world_format"); + WorldFormatSavedData data = (WorldFormatSavedData) world.mapStorage + .loadData(WorldFormatSavedData.class, "cubicchunks.world_format"); if (data == null) { data = new WorldFormatSavedData("cubicchunks.world_format"); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java index c945fc0b..155a36cb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java @@ -11,7 +11,8 @@ import com.cardinalstar.cubicchunks.util.CubePos; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; -public abstract class FeatureCubicGenerator extends SeedBasedCubicGenerator, TGen> { +public abstract class FeatureCubicGenerator + extends SeedBasedCubicGenerator, TGen> { protected FeatureCubicGenerator(int range) { super(range); @@ -50,6 +51,7 @@ private void generateFeature(List> features, TSeed seed) /** * Checks if the given cube has any features, and adds them to the list. The seed is stored in a timed cache, so * they must be immutable. + * * @param rng An RNG that is seeded to a deterministic value for this cube. */ protected abstract void getSeedsImpl(Random rng, int cubeX, int cubeY, int cubeZ, List seeds); @@ -67,9 +69,12 @@ protected final void generate(Random rng, WorldgenFeature feature, WorldV if (feature.affects(pos.getX(), pos.getY(), pos.getZ()) && shouldGenerate(worldView, feature)) { for (var p : feature.getOperations(pos.getX(), pos.getY(), pos.getZ())) { - int x = p.left().x() + box.getX1(); - int y = p.left().y() + box.getY1(); - int z = p.left().z() + box.getZ1(); + int x = p.left() + .x() + box.getX1(); + int y = p.left() + .y() + box.getY1(); + int z = p.left() + .z() + box.getZ1(); place(worldView, feature, x, y, z, p.right()); } @@ -84,12 +89,15 @@ protected boolean shouldGenerate(WorldView worldView, WorldgenFeature fea return true; } - protected void place(WorldView worldView, WorldgenFeature feature, int blockX, int blockY, int blockZ, ImmutableBlockMeta bm) { + protected void place(WorldView worldView, WorldgenFeature feature, int blockX, int blockY, int blockZ, + ImmutableBlockMeta bm) { worldView.setBlock(blockX, blockY, blockZ, bm); } protected abstract int getSeedX(TSeed seed); + protected abstract int getSeedY(TSeed seed); + protected abstract int getSeedZ(TSeed seed); /** Gets a seeds recursions/branches, if any. */ diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java index 400d356d..b6fafb9c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java @@ -15,12 +15,8 @@ public class MapGenCaveFluids implements ICubePopulator { - private static final ForgeDirection[] BLOCK_MASK = { - ForgeDirection.NORTH, - ForgeDirection.SOUTH, - ForgeDirection.EAST, - ForgeDirection.WEST - }; + private static final ForgeDirection[] BLOCK_MASK = { ForgeDirection.NORTH, ForgeDirection.SOUTH, + ForgeDirection.EAST, ForgeDirection.WEST }; private final XSTR rng = new XSTR(); @@ -81,10 +77,12 @@ public void populate(World world, Cube cube) { int globalY = y + cube.getY() * 16; int globalZ = z + cube.getZ() * 16; - fluid.getBlock().onNeighborBlockChange(world, globalX, globalY, globalZ, Blocks.air); + fluid.getBlock() + .onNeighborBlockChange(world, globalX, globalY, globalZ, Blocks.air); world.scheduledUpdatesAreImmediate = true; - fluid.getBlock().updateTick(world, globalX, globalY, globalZ, world.rand); + fluid.getBlock() + .updateTick(world, globalX, globalY, globalZ, world.rand); world.scheduledUpdatesAreImmediate = false; break; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java index b3a8bc7c..521f144e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java @@ -15,7 +15,6 @@ import com.cardinalstar.cubicchunks.util.XSTR; import com.cardinalstar.cubicchunks.worldgen.VanillaWorldGenerator; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; - import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; public class MapGenCavesCubic extends FeatureCubicGenerator { @@ -91,7 +90,14 @@ protected Collection getSeedBranches(CaveSeed caveSeed) { protected boolean shouldGenerate(WorldView worldView, WorldgenFeature feature) { Box box = worldView.getBounds(); - return !scanOuterBoxForWater(worldView, box.getX1(), box.getX2(), box.getY1(), box.getY2(), box.getZ1(), box.getZ2()); + return !scanOuterBoxForWater( + worldView, + box.getX1(), + box.getX2(), + box.getY1(), + box.getY2(), + box.getZ1(), + box.getZ2()); } private static boolean scanOuterBoxForWater(WorldView worldView, int xmin, int xmax, int zmin, int zmax, int ymax, @@ -134,7 +140,8 @@ private boolean isExceptionBiome(BiomeGenBase biome) { } @Override - protected void place(WorldView worldView, WorldgenFeature feature, int x, int y, int z, ImmutableBlockMeta bm) { + protected void place(WorldView worldView, WorldgenFeature feature, int x, int y, int z, + ImmutableBlockMeta bm) { BiomeGenBase biome = worldView.getBiomeGenForBlock(x, y, z); Block block = worldView.getBlock(x, y, z); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java index ce7dbadc..2b31c698 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -12,6 +12,7 @@ import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; + import cpw.mods.fml.common.Optional; public class WorldGenerators { @@ -28,36 +29,36 @@ public static void init() { } private static void initVanilla() { -// CUBIC_VANILLA.terrain().register( -// "noodle-caves", -// new NoodleCaveGenerator(), -// "required-by:caves-all"); -// -// CUBIC_VANILLA.terrain().register( -// "spaghetti-caves", -// new SpaghettiCaveGenerator(), -// "required-by:caves-all"); - - CUBIC_VANILLA.terrain().registerTarget("caves-all"); + // CUBIC_VANILLA.terrain().register( + // "noodle-caves", + // new NoodleCaveGenerator(), + // "required-by:caves-all"); + // + // CUBIC_VANILLA.terrain().register( + // "spaghetti-caves", + // new SpaghettiCaveGenerator(), + // "required-by:caves-all"); + + CUBIC_VANILLA.terrain() + .registerTarget("caves-all"); // TODO: block carver // TODO: pillar caves // TODO: aquifers -// CubeGeneratorsRegistry.registerVanillaPopulator( -// "water-spouts", -// new MapGenCaveFluids(WATER_STILL)); -// -// CubeGeneratorsRegistry.registerVanillaPopulator( -// "lava-spouts", -// new MapGenCaveFluids(LAVA_STILL)); + // CubeGeneratorsRegistry.registerVanillaPopulator( + // "water-spouts", + // new MapGenCaveFluids(WATER_STILL)); + // + // CubeGeneratorsRegistry.registerVanillaPopulator( + // "lava-spouts", + // new MapGenCaveFluids(LAVA_STILL)); } @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) private static void initEFR() { - CUBIC_VANILLA.population().register( - "low-deepslate", - new DeepslateCubePopulator()); + CUBIC_VANILLA.population() + .register("low-deepslate", new DeepslateCubePopulator()); } private static final double CHOOSER_SCALE = 0.01; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java index 79c7c973..42f83edf 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java @@ -16,6 +16,7 @@ import com.cardinalstar.cubicchunks.util.Coords; import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; @@ -50,16 +51,15 @@ public TSeed getSeed() { public void setBlock(int blockX, int blockY, int blockZ, ImmutableBlockMeta bm) { aabb.expand(Coords.blockToLocal(blockX), Coords.blockToLocal(blockY), Coords.blockToLocal(blockZ)); - BlockOperationSet ops = pendingOps.get( - Coords.blockToCube(blockX), - Coords.blockToCube(blockY), - Coords.blockToCube(blockZ)); + BlockOperationSet ops = pendingOps + .get(Coords.blockToCube(blockX), Coords.blockToCube(blockY), Coords.blockToCube(blockZ)); if (ops == null) { - pendingOps.put(ops = new BlockOperationSet( - Coords.blockToCube(blockX), - Coords.blockToCube(blockY), - Coords.blockToCube(blockZ))); + pendingOps.put( + ops = new BlockOperationSet( + Coords.blockToCube(blockX), + Coords.blockToCube(blockY), + Coords.blockToCube(blockZ))); } ops.setBlock(Coords.blockToLocal(blockX), Coords.blockToLocal(blockY), Coords.blockToLocal(blockZ), bm); @@ -118,7 +118,8 @@ public Iterator> iterator() { private final ObjectIterator blockIter = blocks.iterator(); private final Vector3i v = new Vector3i(); - private final ObjectObjectMutablePair pair = ObjectObjectMutablePair.of(v, null); + private final ObjectObjectMutablePair pair = ObjectObjectMutablePair + .of(v, null); @Override public boolean hasNext() { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java index 3a5cffea..c48be45f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java @@ -12,6 +12,7 @@ import com.cardinalstar.cubicchunks.world.cube.blockview.CubeBlockView; import com.cardinalstar.cubicchunks.world.cube.blockview.IMutableBlockView; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; + import ganymedes01.etfuturum.api.DeepslateOreRegistry; import ganymedes01.etfuturum.api.mappings.RegistryMapping; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java index 4dad1d96..0e55fb62 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/FastCubePosMap.java @@ -10,6 +10,7 @@ import com.cardinalstar.cubicchunks.api.XYZAddressable; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java index 4501b53e..b6d3e2fa 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java @@ -17,6 +17,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalCause; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public class NoisePrecalculator & SamplerFactory> { @@ -28,7 +29,7 @@ public class NoisePrecalculator & SamplerFactory> { .maximumSize(1024) .removalListener(notification -> { if (notification.getCause() != RemovalCause.EXPLICIT) { - //noinspection unchecked + // noinspection unchecked releaseData((NoiseData) notification.getValue()); } }) @@ -97,7 +98,8 @@ public void submitPrecalculate(World world, int cubeX, int cubeY, int cubeZ) { public NoiseData takeSampler(World world, int cubeX, int cubeY, int cubeZ) { TaskKey key = new TaskKey(world.provider.dimensionId, cubeX, cubeY, cubeZ); - NoiseData data = cache.asMap().remove(key); + NoiseData data = cache.asMap() + .remove(key); if (data == null) { data = getData(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java index 824b5446..9e03bf6e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java @@ -24,18 +24,21 @@ public class NoodleCaveGenerator implements ICubeGenerator { private enum Layers implements SamplerFactory { Chooser { + @Override public NoiseSampler createSampler(Random rng) { return WorldGenerators.caveChooser(rng); } }, A { + @Override public NoiseSampler createSampler(Random rng) { return new ScaledNoise(new OctavesSampler(rng, 1), SCALE); } }, B { + @Override public NoiseSampler createSampler(Random rng) { return new ScaledNoise(new OctavesSampler(rng, 2), SCALE); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java index c4f8da4a..76d4f4d3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java @@ -23,12 +23,14 @@ public class SpaghettiCaveGenerator implements ICubeGenerator { private enum Layers implements SamplerFactory { A { + @Override public NoiseSampler createSampler(Random rng) { return new ScaledNoise(new OctavesSampler(rng, 3), SCALE); } }, B { + @Override public NoiseSampler createSampler(Random rng) { return new ScaledNoise(new OctavesSampler(rng, 3), SCALE); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java index 84dec547..5998b1fd 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java @@ -3,6 +3,7 @@ public interface BlockNoiseSampler { double sample(int x, int y); + double sample(int x, int y, int z); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java index a440d3b5..a18a9a9e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java @@ -3,6 +3,7 @@ public interface NoiseSampler { double sample(double x, double y); + double sample(double x, double y, double z); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java index 48a26c76..ea0e87f9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java @@ -6,7 +6,9 @@ public class SimplexNoiseSampler implements NoiseSampler { - protected static final int[][] GRADIENTS = new int[][]{{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; + protected static final int[][] GRADIENTS = new int[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, + { 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, + { 1, 1, 0 }, { 0, -1, 1 }, { -1, 1, 0 }, { 0, -1, -1 } }; private static final double SQRT_3 = Math.sqrt(3.0D); private static final double SKEW_FACTOR_2D; private static final double UNSKEW_FACTOR_2D; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java index c12b9709..116f4951 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -41,7 +41,8 @@ private static boolean diff(double a, double b) { } @Override - public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int blockZ, int sx, int sy, int sz, double scaleX, double scaleY, double scaleZ) { + public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int blockZ, int sx, int sy, int sz, + double scaleX, double scaleY, double scaleZ) { long now = System.nanoTime(); if ((now - lastMessage) > 5e9) { @@ -63,8 +64,13 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int zscale = scaleZ; } - // This should never happen because all vanilla noisegens are given constant params, but you never know what mods could do - if (sx != xspan || sy != yspan || sz != zspan || diff(scaleX, xscale) || diff(scaleY, yscale) || diff(scaleZ, zscale)) { + // This should never happen because all vanilla noisegens are given constant params, but you never know what + // mods could do + if (sx != xspan || sy != yspan + || sz != zspan + || diff(scaleX, xscale) + || diff(scaleY, yscale) + || diff(scaleZ, zscale)) { misses++; if (misses > 20) { @@ -167,9 +173,15 @@ public Void call() { PrecalcedVanillaOctaves.super.generateNoiseOctaves( data.data, - key.x, key.y, key.z, - xspan, yspan, zspan, - xscale, yscale, zscale); + key.x, + key.y, + key.z, + xspan, + yspan, + zspan, + xscale, + yscale, + zscale); cache.put(key, data); @@ -185,7 +197,8 @@ private class NoiseData { public double xscale, yscale, zscale; public NoiseData() { - this.data = new double[PrecalcedVanillaOctaves.this.xspan * PrecalcedVanillaOctaves.this.yspan * PrecalcedVanillaOctaves.this.zspan]; + this.data = new double[PrecalcedVanillaOctaves.this.xspan * PrecalcedVanillaOctaves.this.yspan + * PrecalcedVanillaOctaves.this.zspan]; } final boolean matches() { diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index f8843c98..be853d9e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -73,6 +73,7 @@ import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.util.data.BlockMeta; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; @@ -222,16 +223,21 @@ public GenerationResult provideColumn(World world, int columnX, int colum List cubes = new ArrayList<>(); // Ceiling div by 16 - int heightCubes = (data.right().getBounds().getSizeY() + 15) >> 4; + int heightCubes = (data.right() + .getBounds() + .getSizeY() + 15) >> 4; for (int y = 0; y < heightCubes; y++) { - Cube c = new Cube(data.left(), y, data.right().subView(Box.horizontalChunkSlice(y << 4, 16))); + Cube c = new Cube( + data.left(), + y, + data.right() + .subView(Box.horizontalChunkSlice(y << 4, 16))); try { decorator.generate(world, c); } catch (Throwable t) { - CubicChunks.LOGGER - .error("Could not run generation for cube {},{},{}", columnX, y, columnZ, t); + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", columnX, y, columnZ, t); } cubes.add(c); @@ -269,16 +275,21 @@ public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int List sideEffects = new ArrayList<>(); // Ceiling div by 16 - int heightCubes = (data.right().getBounds().getSizeY() + 15) >> 4; + int heightCubes = (data.right() + .getBounds() + .getSizeY() + 15) >> 4; for (int y = 0; y < heightCubes; y++) { - Cube c = new Cube(chunk, y, data.right().subView(Box.horizontalChunkSlice(y << 4, 16))); + Cube c = new Cube( + chunk, + y, + data.right() + .subView(Box.horizontalChunkSlice(y << 4, 16))); try { decorator.generate(world, c); } catch (Throwable t) { - CubicChunks.LOGGER - .error("Could not run generation for cube {},{},{}", cubeX, y, cubeZ, t); + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, y, cubeZ, t); } if (y == cubeY) { @@ -296,8 +307,7 @@ public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int try { decorator.generate(world, cube); } catch (Throwable t) { - CubicChunks.LOGGER - .error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); } return new GenerationResult<>(cube); @@ -399,15 +409,19 @@ public void populate(Cube cube) { if (cy >= 0 && cy < 16) { for (int x = -1; x <= 1; x++) { for (int z = -1; z <= 1; z++) { - ((IColumnInternal) loader.getColumn(cx + x, cz + z, Requirement.GENERATE)).recalculateStagingHeightmap(); + ((IColumnInternal) loader.getColumn(cx + x, cz + z, Requirement.GENERATE)) + .recalculateStagingHeightmap(); } } for (int dx = -1; dx <= 0; dx++) { for (int dz = -1; dz <= 0; dz++) { - // For some bizarre reason, MC offsets its block positions by +8 when populating. This means that when a chunk - // gets populated, it's actually populating the 16x16 blocks centered on the +x/+z corner. This is how every MC - // populator works for some reason (who decided this???). As a result, we need to generate the 3 columns in the + // For some bizarre reason, MC offsets its block positions by +8 when populating. This means + // that when a chunk + // gets populated, it's actually populating the 16x16 blocks centered on the +x/+z corner. This + // is how every MC + // populator works for some reason (who decided this???). As a result, we need to generate the 3 + // columns in the // negative directions (-x,z, x,-z, -x,-z). populateChunk(loader, cx + dx, cz + dz); @@ -450,37 +464,19 @@ public void populate(Cube cube) { } } } catch (Throwable t) { - CubicChunks.LOGGER.error( - "Could not run non-vanilla population for cube {},{},{}", - cx, - cy, - cz, - t); + CubicChunks.LOGGER.error("Could not run non-vanilla population for cube {},{},{}", cx, cy, cz, t); } finally { WorldgenHangWatchdog.endWorldGen(); loader.uncacheCubes(); } } - private static final Vector3ic[] AFFECTED_CUBES = { - new Vector3i(1, 0, 0), - new Vector3i(0, 1, 0), - new Vector3i(1, 1, 0), - new Vector3i(0, 0, 1), - new Vector3i(1, 0, 1), - new Vector3i(0, 1, 1), - new Vector3i(1, 1, 1), - }; - - private static final short[] CUBE_FLAGS = { - Cube.POP_100, - Cube.POP_010, - Cube.POP_110, - Cube.POP_001, - Cube.POP_101, - Cube.POP_011, - Cube.POP_111, - }; + private static final Vector3ic[] AFFECTED_CUBES = { new Vector3i(1, 0, 0), new Vector3i(0, 1, 0), + new Vector3i(1, 1, 0), new Vector3i(0, 0, 1), new Vector3i(1, 0, 1), new Vector3i(0, 1, 1), + new Vector3i(1, 1, 1), }; + + private static final short[] CUBE_FLAGS = { Cube.POP_100, Cube.POP_010, Cube.POP_110, Cube.POP_001, Cube.POP_101, + Cube.POP_011, Cube.POP_111, }; private Box getCubesToGenerate(int x, int y, int z) { if (y >= 0 && y < 16) { @@ -554,8 +550,10 @@ private void applyModGenerators(int x, int z, World world, IChunkProvider vanill @Override public void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel wanted) { - boolean generate = actual.ordinal() < CubeInitLevel.Generated.ordinal() && wanted.ordinal() >= CubeInitLevel.Generated.ordinal(); - boolean populate = actual.ordinal() < CubeInitLevel.Populated.ordinal() && wanted.ordinal() >= CubeInitLevel.Populated.ordinal(); + boolean generate = actual.ordinal() < CubeInitLevel.Generated.ordinal() + && wanted.ordinal() >= CubeInitLevel.Generated.ordinal(); + boolean populate = actual.ordinal() < CubeInitLevel.Populated.ordinal() + && wanted.ordinal() >= CubeInitLevel.Populated.ordinal(); if (generate) { if (vanilla instanceof Precalculable precalc) { From b114a6b3615dcfee5c30d328885bbac38c3829ff Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sat, 15 Nov 2025 20:52:14 -0500 Subject: [PATCH 08/22] Works somewhat better --- dependencies.gradle | 7 +- gradle.properties | 2 +- repositories.gradle | 1 + .../cardinalstar/cubicchunks/CubicChunks.java | 2 + .../api/world/storage/ICubicStorage.java | 8 +- .../cubicchunks/async/TaskPool.java | 230 ++++++++------- .../common/MixinExtendedBlockStorage.java | 2 +- .../early/common/MixinRegionFileCache.java | 4 +- .../worldgen/MixinChunkProviderGenerate.java | 19 +- .../cubicchunks/server/CubeWatcher.java | 11 +- .../server/CubicPlayerManager.java | 16 +- .../server/chunkio/CCNBTUtils.java | 48 +++ .../cubicchunks/server/chunkio/CubeIO.java | 86 +++--- .../server/chunkio/CubeLoaderServer.java | 31 +- .../server/chunkio/IONbtWriter.java | 10 - .../server/chunkio/RegionCubeStorage.java | 155 +++++----- .../cubicchunks/util/DataUtils.java | 273 ++++++++++++++++++ .../worldgen/data/NoisePrecalculator.java | 70 +++-- .../vanilla/PrecalcedVanillaOctaves.java | 51 +++- .../worldgen/VanillaWorldGenerator.java | 19 +- 20 files changed, 723 insertions(+), 322 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java diff --git a/dependencies.gradle b/dependencies.gradle index 2b61c462..2e216481 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -35,13 +35,16 @@ */ dependencies { implementation("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") - implementation("io.github.opencubicchunks:regionlib:0.78.0-SNAPSHOT") + implementation("com.github.GTNewHorizons:RegionLib:99.99.99:dev") runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.4-GTNH:dev") - runtimeOnlyNonPublishable(rfg.deobf("curse.maven:spark-361579:4271867")) +// runtimeOnlyNonPublishable(rfg.deobf("curse.maven:spark-361579:4271867")) compileOnly("org.jetbrains:annotations:26.0.2") + compileOnly('org.projectlombok:lombok:1.18.34') + annotationProcessor('org.projectlombok:lombok:1.18.34') + devOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.2.21-GTNH-daily:dev") // devOnlyNonPublishable("com.github.GTNewHorizons:Angelica:1.0.0-beta56:dev") diff --git a/gradle.properties b/gradle.properties index 021fe430..45879f0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -82,7 +82,7 @@ usesMixins = true separateMixinSourceSet = # Adds some debug arguments like verbose output and class export. -usesMixinDebug = true +usesMixinDebug = false # Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. mixinPlugin = diff --git a/repositories.gradle b/repositories.gradle index 078ccbd8..e2bb17bf 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -5,4 +5,5 @@ repositories { maven { setUrl("https://oss.sonatype.org/service/local/repositories/snapshots/content/") } + mavenLocal() } diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index c9c3066a..72d625c3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -42,6 +42,7 @@ import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; import com.cardinalstar.cubicchunks.api.world.storage.StorageFormatFactory; import com.cardinalstar.cubicchunks.api.worldtype.VanillaCubicWorldType; +import com.cardinalstar.cubicchunks.async.TaskPool; import com.cardinalstar.cubicchunks.event.handlers.ClientEventHandler; import com.cardinalstar.cubicchunks.event.handlers.CommonEventHandler; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldSettings; @@ -155,6 +156,7 @@ public String call() throws Exception { .activeModContainer()); holder.testVanillaAcceptance(); WorldGenerators.init(); + TaskPool.init(); } @Mod.EventHandler diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/ICubicStorage.java b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/ICubicStorage.java index 41a03881..b2994820 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/ICubicStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/world/storage/ICubicStorage.java @@ -23,9 +23,9 @@ import java.io.Flushable; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Collection; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -249,10 +249,10 @@ default void writeBatch(NBTBatch batch) throws IOException { */ class PosBatch { - public final Set columns; - public final Set cubes; + public final Collection columns; + public final Collection cubes; - public PosBatch(Set columns, Set cubes) { + public PosBatch(Collection columns, Collection cubes) { this.columns = Objects.requireNonNull(columns, "columns"); this.cubes = Objects.requireNonNull(cubes, "cubes"); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java index 298cab16..0cd9ec42 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -1,16 +1,11 @@ package com.cardinalstar.cubicchunks.async; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -20,7 +15,7 @@ import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.google.common.util.concurrent.AbstractFuture; -@SuppressWarnings("unused") +@SuppressWarnings({ "unused", "UnusedReturnValue" }) public class TaskPool { private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0); @@ -33,145 +28,166 @@ public class TaskPool { return thread; }; - private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor( - 1, - CubicChunksConfig.optimizations.backgroundThreads, - 60, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(1024), - THREAD_FACTORY, - new DiscardPolicy()); + private static final Object QUEUE_LOCK = new Object(); + private static final ArrayDeque> TASK_QUEUE = new ArrayDeque<>(1024); - private static final ScheduledExecutorService SCHEDULED = Executors - .newSingleThreadScheduledExecutor(THREAD_FACTORY); + private static final List THREADS = new ArrayList<>(); - // static { - // SCHEDULED.scheduleAtFixedRate(TaskPool::flushLastTask, 1, 1, TimeUnit.MILLISECONDS); - // } + public static void init() { + THREADS.clear(); - // private static final Object lock = new Object(); - // private static Task lastTask; + int count = CubicChunksConfig.optimizations.backgroundThreads; - // private static final long TASK_BATCHING_PERIOD = Duration.ofMillis(1).toNanos(); - - public static Future submit(ITask task) { - return submit(task, null); + for (int i = 0; i < count; i++) { + Thread worker = new WorkerThread(); + THREADS.add(worker); + worker.start(); + } } - public static Future submit(ITask task, @Nullable Consumer callback) { - long now = System.nanoTime(); - - synchronized (lock) { - // if (lastTask != null) { - // Task merged; - // - // if (lastTask.submitTime + TASK_BATCHING_PERIOD < now) { - // POOL.submit(lastTask); - // lastTask = null; - // } else if ((merged = lastTask.tryMerge(task, now, callback)) != null) { - // return merged; - // } - // } - - Task future = new Task<>(task, now, callback); - // lastTask = future; - POOL.submit(future); - return future; + private static class WorkerThread extends Thread { + + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + public WorkerThread() { + super(String.format("CC BG Thread %d", THREAD_COUNTER.incrementAndGet())); + + setDaemon(true); } - } - // private static void flushLastTask() { - // synchronized (lock) { - // if (lastTask != null) { - // if (lastTask.submitTime + TASK_BATCHING_PERIOD < System.nanoTime()) { - // POOL.submit(lastTask); - // lastTask = null; - // } - // } - // } - // } - - public interface ITask extends Callable { - - /// Merges another task into this one to batch their work, if possible - default boolean canMerge(ITask other) { - return false; + @Override + public void run() { + while (!cancelled.get()) { + TaskContainer task; + + synchronized (QUEUE_LOCK) { + if (TASK_QUEUE.isEmpty()) { + try { + QUEUE_LOCK.wait(); + } catch (InterruptedException e) { + continue; + } + } + + task = TASK_QUEUE.poll(); + } + + if (task == null) continue; + + task.run(); + } } + } - /// Executes all merged tasks - default T callMerged(List> mergedTasks) throws Exception { - throw new UnsupportedOperationException(); + public static final ITaskExecutor RUNNABLE_EXECUTOR = tasks -> { + for (var task : tasks) { + task.getTask().run(); + task.finish(null); } + }; + + public static Future submit(Runnable task) { + return submit(RUNNABLE_EXECUTOR, task, null); } - public interface ITaskFuture { + public static Future submit(ITaskExecutor executor, TTask task) { + return submit(executor, task, null); + } - ITask getTask(); + public static Future submit(ITaskExecutor executor, TTask task, @Nullable Consumer callback) { + TaskFuture future = new TaskFuture<>(task, callback); - void finish(T value); + synchronized (QUEUE_LOCK) { + if (!TASK_QUEUE.isEmpty()) { + TaskContainer end = TASK_QUEUE.peekLast(); - void fail(Exception ex); - } + if (end.executor == executor) { + @SuppressWarnings("unchecked") + TaskContainer end2 = (TaskContainer) end; - private static class Task extends AbstractFuture implements Runnable, ITaskFuture { + //noinspection unchecked + if (end2.executor.canMerge((List>) (List) end2.tasks, task)) { + end2.tasks.add(future); + QUEUE_LOCK.notify(); + return future; + } + } + } - private final ITask task; - public final long submitTime; - @Nullable - private final Consumer callback; + TaskContainer container = new TaskContainer<>(executor); + container.tasks.add(future); - private List> merged = null; + TASK_QUEUE.addLast(container); + QUEUE_LOCK.notify(); + return future; + } + } - public Task(ITask task, long submitTime, @Nullable Consumer callback) { - this.task = task; - this.submitTime = submitTime; - this.callback = callback; + public interface ITaskExecutor { + + void execute(List> tasks); + + default boolean canMerge(List> tasks, TTask task) { + return tasks.size() < 128; } + } - public synchronized Task tryMerge(ITask other, long now, @Nullable Consumer callback) { - if (this.isDone()) return null; + public interface ITaskFuture { - if (task.canMerge(other)) { - if (merged == null) merged = new ArrayList<>(1); + TTask getTask(); - Task otherTask = new Task<>(other, now, callback); + void finish(TResult value); - merged.add(otherTask); + void fail(Throwable t); + } - return otherTask; - } + private static class TaskFuture extends AbstractFuture implements ITaskFuture { - return null; - } + public final TTask task; + @Nullable + private final Consumer callback; - @Override - public synchronized void run() { - try { - if (merged != null) { - // noinspection unchecked - finish(task.callMerged((List>) (List) merged)); - } else { - finish(task.call()); - } - } catch (Exception e) { - CubicChunks.LOGGER.error("Could not run background task", e); - } + public TaskFuture(TTask task, @Nullable Consumer callback) { + this.task = task; + this.callback = callback; } @Override - public ITask getTask() { + public TTask getTask() { return task; } @Override - public void finish(T value) { + public void finish(TResult value) { set(value); if (callback != null) callback.accept(value); } @Override - public void fail(Exception ex) { - setException(ex); + public void fail(Throwable t) { + setException(t); + } + } + + private static class TaskContainer implements Runnable { + + private final ITaskExecutor executor; + private final List> tasks = new ArrayList<>(1); + + public TaskContainer(ITaskExecutor executor) { + this.executor = executor; + } + + @Override + public void run() { + try { + tasks.removeIf(TaskFuture::isCancelled); + + //noinspection unchecked + executor.execute((List>) (List) tasks); + } catch (Exception e) { + CubicChunks.LOGGER.error("Could not run background task", e); + } } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java index 9bf3eb5a..719c58d8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java @@ -20,7 +20,7 @@ public class MixinExtendedBlockStorage { @Redirect( method = "getBlockByExtId", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;getBlockById(I)Lnet/minecraft/block/Block;")) - public Block optimizeGetBlock(int id) { + public final Block optimizeGetBlock(int id) { if (id == prevId) return prevBlock; prevId = id; diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java index 472922f0..64041054 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java @@ -31,8 +31,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import cubicchunks.regionlib.lib.provider.SharedCachedRegionProvider; - // a hook for flush() // many mods already assume AnvilSaveHandler is always used, so we assume the same and hope for the best @ParametersAreNonnullByDefault @@ -41,6 +39,6 @@ public class MixinRegionFileCache { @Inject(method = "clearRegionFileReferences", at = @At("HEAD")) private static void onClearRefs(CallbackInfo cbi) throws IOException { - SharedCachedRegionProvider.clearRegions(); +// SharedCachedRegionProvider.clearRegions(); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java index 2d708e5d..b681b1a9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java @@ -1,5 +1,7 @@ package com.cardinalstar.cubicchunks.mixin.early.common.worldgen; +import java.util.Random; + import net.minecraft.block.Block; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.World; @@ -15,10 +17,12 @@ import org.spongepowered.asm.mixin.injection.Redirect; import com.cardinalstar.cubicchunks.api.world.Precalculable; +import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalcedVanillaOctaves; import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalculableNoise; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; - +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -37,11 +41,12 @@ public class MixinChunkProviderGenerate implements Precalculable { @Shadow public NoiseGeneratorOctaves noiseGen6; - // @Redirect(method = "", at = @At(value = "NEW", target = - // "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) - // public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves) { - // return new PrecalcedVanillaOctaves(random, octaves); - // } + @WrapOperation( + method = "", at = @At( + value = "NEW", target = "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) + public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves, Operation original) { + return new PrecalcedVanillaOctaves(original.call(random, octaves)); + } @Definition( id = "caveGenerator", @@ -72,6 +77,8 @@ public void precalculate(int cubeX, int cubeY, int cubeZ) { long chunk = chunkGenFIFO.dequeueLong(); chunkGenDebounce.remove(chunk); } + } else { + return; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java index 48e33551..8b704a0b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java @@ -101,9 +101,13 @@ public void onCubeLoaded(Cube c) { return; } - this.cube = c; - this.cube.getTickets() - .add(this); + if (this.cube != c) { + if (this.cube != null) this.cube.getTickets().remove(this); + + this.cube = c; + this.cube.getTickets() + .add(this); + } if (this.request != null) this.request.cancel(); this.request = null; @@ -176,6 +180,7 @@ void removePlayer(EntityPlayerMP player) { if (this.players.isEmpty()) { cubicPlayerManager.removeEntry(this); + if (this.request != null) this.request.cancel(); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index 4252ae63..115a3eda 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -70,9 +70,7 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.SetMultimap; - -import gnu.trove.map.TIntObjectMap; -import gnu.trove.map.hash.TIntObjectHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; @@ -94,7 +92,7 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallb /** * Mapping if entityId to PlayerCubeMap.PlayerWrapper objects. */ - private final TIntObjectMap players = new TIntObjectHashMap<>(); + private final Int2ObjectOpenHashMap players = new Int2ObjectOpenHashMap<>(); /** * Mapping of Cube positions to CubeWatchers (Cube equivalent of PlayerManager.PlayerInstance). @@ -126,7 +124,7 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallb */ private final WatchersSortingList3D watchersToAddPlayersTo = new WatchersSortingList3D<>( 0, - () -> players.valueCollection() + () -> players.values() .stream() .map(p -> p.playerEntity) .collect(Collectors.toList())); @@ -140,7 +138,7 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallb */ private final WatchersSortingList3D cubesToSendToClients = new WatchersSortingList3D<>( 1, - () -> players.valueCollection() + () -> players.values() .stream() .map(p -> p.playerEntity) .collect(Collectors.toList())); @@ -154,14 +152,14 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallb */ private final WatchersSortingList2D columnsToSendToClients = new WatchersSortingList2D<>( 3, - () -> players.valueCollection() + () -> players.values() .stream() .map(p -> p.playerEntity) .collect(Collectors.toList())); private final WatchersSortingList3D tickableCubeTracker = new WatchersSortingList3D<>( 5, - () -> players.valueCollection() + () -> players.values() .stream() .map(p -> p.playerEntity) .collect(Collectors.toList())); @@ -776,7 +774,7 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe return; } - for (PlayerWrapper playerWrapper : this.players.valueCollection()) { + for (PlayerWrapper playerWrapper : this.players.values()) { EntityPlayerMP player = playerWrapper.playerEntity; CubePos playerPos = playerWrapper.getManagedCubePos(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java new file mode 100644 index 00000000..4aaf9bc8 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java @@ -0,0 +1,48 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTSizeTracker; +import net.minecraft.nbt.NBTTagCompound; + +import org.apache.commons.io.IOUtils; + +public class CCNBTUtils { + + public static NBTTagCompound loadTag(byte[] data) throws IOException { + if (data[0] == (byte) 0x1f && data[1] == (byte) 0x8b) { + GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(data)); + data = IOUtils.toByteArray(gzip); + } + + return CompressedStreamTools.func_152456_a(new DataInputStream(new ByteArrayInputStream(data)), NBTSizeTracker.field_152451_a); + } + + public static byte[] saveTag(NBTTagCompound tag, boolean compress) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + CompressedStreamTools.write(tag, new DataOutputStream(baos)); + + byte[] data = baos.toByteArray(); + + if (compress) { + ByteArrayOutputStream zipped = new ByteArrayOutputStream(data.length); + + try (GZIPOutputStream zipstream = new GZIPOutputStream(zipped)) { + IOUtils.write(data, zipstream); + zipstream.flush(); + } + + data = zipped.toByteArray(); + } + + return data; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index ce30a550..fd49ae80 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -17,20 +17,24 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; +import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage.PosBatch; import com.cardinalstar.cubicchunks.async.TaskPool; -import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskExecutor; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskFuture; import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.DataUtils; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - import it.unimi.dsi.fastutil.Pair; public class CubeIO implements ICubeIO, IThreadedFileIO { private final ICubicStorage storage; private final IPreloadFailureDelegate preloadFailures; + private final ITaskExecutor columnLoadExecutor; + private final ITaskExecutor cubeLoadExecutor; private final LinkedTransferQueue> columnQueue = new LinkedTransferQueue<>(); private final LinkedTransferQueue> cubeQueue = new LinkedTransferQueue<>(); @@ -57,6 +61,44 @@ public class CubeIO implements ICubeIO, IThreadedFileIO { public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { this.storage = storage; this.preloadFailures = preloadFailures; + + columnLoadExecutor = tasks -> { + try { + var result = storage.readBatch( + new PosBatch( + DataUtils.mapToList(tasks, ITaskFuture::getTask), + Collections.emptyList())); + + for (var task : tasks) { + NBTTagCompound tag = result.columns.get(task.getTask()); + + task.finish(tag); + } + } catch (IOException e) { + for (var task : tasks) { + task.fail(e); + } + } + }; + + cubeLoadExecutor = tasks -> { + try { + var result = storage.readBatch( + new PosBatch( + Collections.emptyList(), + DataUtils.mapToList(tasks, ITaskFuture::getTask))); + + for (var task : tasks) { + NBTTagCompound tag = result.cubes.get(task.getTask()); + + task.finish(tag); + } + } catch (IOException e) { + for (var task : tasks) { + task.fail(e); + } + } + }; } @Override @@ -104,8 +146,6 @@ public NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureExcept if (tag == null) return null; - columnCache.put(pos, (NBTTagCompound) tag.copy()); - return tag; } catch (IOException e) { CubicChunks.LOGGER.error("Could not read column {}", pos, e); @@ -128,8 +168,6 @@ public NBTTagCompound loadCube(CubePos pos) throws LoadFailureException { if (tag == null) return null; - cubeCache.put(pos, (NBTTagCompound) tag.copy()); - return tag; } catch (IOException e) { CubicChunks.LOGGER.error("Could not read cube {}", pos, e); @@ -244,7 +282,7 @@ public boolean writeNextIO() { @Override public void preloadColumn(ChunkCoordIntPair pos) { - TaskPool.submit(new LoadColumnTask(storage, pos), tag -> { + TaskPool.submit(columnLoadExecutor, pos, tag -> { if (tag == null) { if (preloadFailures != null) preloadFailures.onColumnPreloadFailed(pos); } else { @@ -255,7 +293,7 @@ public void preloadColumn(ChunkCoordIntPair pos) { @Override public void preloadCube(CubePos pos, CubeInitLevel wanted) { - TaskPool.submit(new LoadCubeTask(storage, pos), tag -> { + TaskPool.submit(cubeLoadExecutor, pos, tag -> { CubeInitLevel actual = tag == null ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag); if (tag == null || actual.ordinal() < wanted.ordinal()) { @@ -269,36 +307,4 @@ public void preloadCube(CubePos pos, CubeInitLevel wanted) { } }); } - - private static class LoadColumnTask implements ITask { - - private final ICubicStorage storage; - private final ChunkCoordIntPair pos; - - public LoadColumnTask(ICubicStorage storage, ChunkCoordIntPair pos) { - this.storage = storage; - this.pos = pos; - } - - @Override - public NBTTagCompound call() throws Exception { - return storage.readColumn(pos); - } - } - - private static class LoadCubeTask implements ITask { - - private final ICubicStorage storage; - private final CubePos pos; - - public LoadCubeTask(ICubicStorage storage, CubePos pos) { - this.storage = storage; - this.pos = pos; - } - - @Override - public NBTTagCompound call() throws Exception { - return storage.readCube(pos); - } - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index 4f2fd26c..4256f3c8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -11,6 +11,7 @@ import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.common.ForgeChunkManager; +import net.minecraftforge.event.world.ChunkDataEvent; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; @@ -24,13 +25,13 @@ import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.util.Array3D; +import com.cardinalstar.cubicchunks.util.BlockPosMap; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; - import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; public class CubeLoaderServer implements ICubeLoader { @@ -343,13 +344,6 @@ public void doGC() { unloadCube(pos.getX(), pos.getY(), pos.getZ()); } - if (!pendingCubeUnloads.isEmpty()) { - CubicChunks.LOGGER.info( - "Garbage collected {} cubes (now have {} cubes loaded)", - pendingCubeUnloads.size(), - cubes.getSize()); - } - List pendingColumnUnloads = new ArrayList<>(); for (ColumnInfo columnInfo : columns) { @@ -372,13 +366,6 @@ public void doGC() { for (ColumnInfo column : pendingColumnUnloads) { unloadColumn(column); } - - if (!pendingColumnUnloads.isEmpty()) { - CubicChunks.LOGGER.info( - "Garbage collected {} columns (now have {} columns loaded)", - pendingColumnUnloads.size(), - columns.getSize()); - } } @Override @@ -395,6 +382,10 @@ private void handleSideEffects(GenerationResult result) { for (Chunk column : result.columnSideEffects) { ColumnInfo info = columns.get(column.xPosition, column.zPosition); + if (info != null && info.column != null) { + CubicChunks.LOGGER.warn("Worldgen side-effect replaced column at {},{}!", column.xPosition, column.zPosition); + } + if (info == null) { info = new ColumnInfo(column.xPosition, column.zPosition); columns.put(info); @@ -409,6 +400,10 @@ private void handleSideEffects(GenerationResult result) { for (Cube cube : result.cubeSideEffects) { CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); + if (info != null && info.cube != null) { + CubicChunks.LOGGER.warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); + } + if (info == null) { info = new CubeInfo(cube.getX(), cube.getY(), cube.getZ()); info.column = columns.get(cube.getX(), cube.getZ()); @@ -504,6 +499,8 @@ private boolean loadColumn() { if (column == null) return false; + EVENT_BUS.post(new ChunkDataEvent.Load(column, tag)); + onColumnLoaded(); this.tag = null; @@ -512,10 +509,14 @@ private boolean loadColumn() { } public void onColumnLoaded() { + column.lastSaveTime = world.getTotalWorldTime(); + ((IColumnInternal) column).setColumn(true); column.onChunkLoad(); + CubeLoaderServer.this.generator.recreateStructures(column); + if (pauseLoadCalls == 0) { callback.onColumnLoaded(column); } else { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java index be39c14e..c17b0077 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java @@ -22,8 +22,6 @@ import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -31,7 +29,6 @@ import net.minecraft.block.Block; import net.minecraft.entity.Entity; -import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; @@ -52,19 +49,12 @@ import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; - import cpw.mods.fml.common.FMLLog; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @ParametersAreNonnullByDefault class IONbtWriter { - static byte[] writeNbtBytes(NBTTagCompound nbt) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - CompressedStreamTools.writeCompressed(nbt, buf); - return buf.toByteArray(); - } - static NBTTagCompound write(Chunk column) { NBTTagCompound columnNbt = new NBTTagCompound(); NBTTagCompound level = new NBTTagCompound(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java index 67bbc252..a37dd74b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java @@ -20,7 +20,6 @@ */ package com.cardinalstar.cubicchunks.server.chunkio; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; @@ -38,23 +37,26 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.ChunkCoordIntPair; +import org.jetbrains.annotations.NotNull; + import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; import com.cardinalstar.cubicchunks.server.chunkio.region.ShadowPagingRegion; import com.cardinalstar.cubicchunks.util.CubePos; - +import com.cardinalstar.cubicchunks.util.DataUtils; import cubicchunks.regionlib.impl.EntryLocation2D; import cubicchunks.regionlib.impl.EntryLocation3D; import cubicchunks.regionlib.impl.SaveCubeColumns; import cubicchunks.regionlib.impl.save.SaveSection2D; import cubicchunks.regionlib.impl.save.SaveSection3D; import cubicchunks.regionlib.lib.ExtRegion; +import cubicchunks.regionlib.lib.factory.SimpleRegionFactory; import cubicchunks.regionlib.lib.provider.SharedCachedRegionProvider; -import cubicchunks.regionlib.lib.provider.SimpleRegionProvider; import cubicchunks.regionlib.util.Utils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.UnpooledByteBufAllocator; +import it.unimi.dsi.fastutil.Pair; /** * Implementation of {@link ICubicStorage} for the Cubic Chunks' standard Anvil3d storage format. @@ -74,7 +76,7 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { @SuppressWarnings("unchecked") SaveSection2D section2d = new SaveSection2D( new SharedCachedRegionProvider<>( - new SimpleRegionProvider<>( + new SimpleRegionFactory<>( new EntryLocation2D.Provider(), part2d, (keyProv, r) -> ShadowPagingRegion.builder() @@ -84,23 +86,21 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setSectorSize(512) .build(), (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName())))), + part2d.resolve( + key.getName())))), new SharedCachedRegionProvider<>( - new SimpleRegionProvider<>( + new SimpleRegionFactory<>( new EntryLocation2D.Provider(), part2d, (keyProvider, regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName() + ".ext"))))); + part2d.resolve( + key.getName() + ".ext"))))); @SuppressWarnings("unchecked") SaveSection3D section3d = new SaveSection3D( new SharedCachedRegionProvider<>( - new SimpleRegionProvider<>( + new SimpleRegionFactory<>( new EntryLocation3D.Provider(), part3d, (keyProv, r) -> ShadowPagingRegion.builder() @@ -110,19 +110,17 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setSectorSize(512) .build(), (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName())))), + part3d.resolve( + key.getName())))), new SharedCachedRegionProvider<>( - new SimpleRegionProvider<>( + new SimpleRegionFactory<>( new EntryLocation3D.Provider(), part3d, (keyProvider, regionKey) -> new ExtRegion<>(part3d, Collections.emptyList(), keyProvider, regionKey), (dir, key) -> Files.exists( - dir.resolve( - key.getRegionKey() - .getName() + ".ext"))))); + part3d.resolve( + key.getName() + ".ext"))))); return new SaveCubeColumns(section2d, section3d); } else { @@ -156,22 +154,52 @@ public NBTTagCompound readColumn(ChunkCoordIntPair pos) throws IOException { // Files.exists() check for every cube/column (which // is really expensive on windows) Optional data = this.save.load(new EntryLocation2D(pos.chunkXPos, pos.chunkZPos), true); - return data.isPresent() ? CompressedStreamTools.readCompressed( - new ByteArrayInputStream( - data.get() - .array())) // decompress and parse NBT - : null; // column doesn't exist + if (!data.isPresent()) return null; + + return CCNBTUtils.loadTag(data.get().array()); } @Override public NBTTagCompound readCube(CubePos pos) throws IOException { // see comment in readColumn Optional data = this.save.load(new EntryLocation3D(pos.getX(), pos.getY(), pos.getZ()), true); - return data.isPresent() ? CompressedStreamTools.readCompressed( - new ByteArrayInputStream( - data.get() - .array())) // decompress and parse NBT - : null; // cube doesn't exist + if (!data.isPresent()) return null; + + return CCNBTUtils.loadTag(data.get().array()); + } + + @Override + public @NotNull NBTBatch readBatch(PosBatch positions) throws IOException { + var columns = this.save.load2D(DataUtils.mapToList(positions.columns, c -> new EntryLocation2D(c.chunkXPos, c.chunkZPos)), false); + var cubes = this.save.load3D(DataUtils.mapToList(positions.cubes, c -> new EntryLocation3D(c.getX(), c.getY(), c.getZ())), false); + + var columnTags = columns.read.entrySet() + .parallelStream() + .map(e -> { + try { + return Pair.of( + new ChunkCoordIntPair(e.getKey().getEntryX(), e.getKey().getEntryZ()), + CCNBTUtils.loadTag(e.getValue().array())); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }) + .collect(Collectors.toMap(Pair::left, Pair::right)); + + var cubeTags = cubes.read.entrySet() + .parallelStream() + .map(e -> { + try { + return Pair.of( + new CubePos(e.getKey().getEntryX(), e.getKey().getEntryY(), e.getKey().getEntryZ()), + CCNBTUtils.loadTag(e.getValue().array())); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }) + .collect(Collectors.toMap(Pair::left, Pair::right)); + + return new NBTBatch(columnTags, cubeTags); } @Override @@ -204,45 +232,36 @@ public void writeCube(CubePos pos, NBTTagCompound nbt) throws IOException { @Override public void writeBatch(NBTBatch batch) throws IOException { - Map compressedColumns = Collections.emptyMap(); - Map compressedCubes = Collections.emptyMap(); - try { - // compress NBT data - compressedColumns = this - .compressNBTForBatchWrite(batch.columns, pos -> new EntryLocation2D(pos.chunkXPos, pos.chunkZPos)); - compressedCubes = this - .compressNBTForBatchWrite(batch.cubes, pos -> new EntryLocation3D(pos.getX(), pos.getY(), pos.getZ())); + Map compressedColumns = Collections.emptyMap(); + Map compressedCubes = Collections.emptyMap(); + // compress NBT data + compressedColumns = this + .compressNBTForBatchWrite(batch.columns, pos -> new EntryLocation2D(pos.chunkXPos, pos.chunkZPos)); + compressedCubes = this + .compressNBTForBatchWrite(batch.cubes, pos -> new EntryLocation3D(pos.getX(), pos.getY(), pos.getZ())); - // write compressed data to disk - if (!compressedColumns.isEmpty()) { - this.save.save2d( - compressedColumns.entrySet() - .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue() - .nioBuffer()))); - } - if (!compressedCubes.isEmpty()) { - this.save.save3d( - compressedCubes.entrySet() - .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue() - .nioBuffer()))); - } - } finally { - compressedColumns.values() - .forEach(ByteBuf::release); - compressedCubes.values() - .forEach(ByteBuf::release); + // write compressed data to disk + if (!compressedColumns.isEmpty()) { + this.save.save2d( + compressedColumns.entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> ByteBuffer.wrap(entry.getValue())))); + } + if (!compressedCubes.isEmpty()) { + this.save.save3d( + compressedCubes.entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> ByteBuffer.wrap(entry.getValue())))); } } - private Map compressNBTForBatchWrite(Map nbt, + private Map compressNBTForBatchWrite(Map nbt, Function keyMappingFunction) throws IOException { if (nbt.isEmpty()) { // avoid somewhat expensive stream creation if there are no entries return Collections.emptyMap(); @@ -256,17 +275,11 @@ private Map compressNBTForBatchWrite(Map keyMappingFunction.apply(entry.getKey()), entry -> { - ByteBuf compressedBuf = UnpooledByteBufAllocator.DEFAULT.ioBuffer(); try { - // encode and compress nbt data - CompressedStreamTools.writeCompressed(entry.getValue(), new ByteBufOutputStream(compressedBuf)); - - return compressedBuf.retain(); + return CCNBTUtils.saveTag(entry.getValue(), false); } catch (IOException e) { // wrap exception so that we can throw it from inside the lambda throw new UncheckedIOException(e); - } finally { - compressedBuf.release(); } })); } catch (UncheckedIOException e) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java new file mode 100644 index 00000000..1f70ced1 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java @@ -0,0 +1,273 @@ +package com.cardinalstar.cubicchunks.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; + +import cpw.mods.fml.relauncher.ReflectionHelper; + +public class DataUtils { + + public static List mapToList(Collection in, Function mapper) { + if (in == null) return null; + + List out = new ArrayList<>(in.size()); + + for (S s : in) { + out.add(mapper.apply(s)); + } + + return out; + } + + public static List mapToList(S[] in, Function mapper) { + if (in == null) return null; + + List out = new ArrayList<>(in.length); + + for (S s : in) { + out.add(mapper.apply(s)); + } + + return out; + } + + public static T[] mapToArray(Collection in, IntFunction ctor, Function mapper) { + if (in == null) return null; + + T[] out = ctor.apply(in.size()); + + Iterator iter = in.iterator(); + for (int i = 0; i < out.length && iter.hasNext(); i++) { + out[i] = mapper.apply(iter.next()); + } + + return out; + } + + public static T[] mapToArray(S[] in, IntFunction ctor, Function mapper) { + if (in == null) return null; + + T[] out = ctor.apply(in.length); + + for (int i = 0; i < out.length; i++) { + out[i] = mapper.apply(in[i]); + } + + return out; + } + + public static int indexOf(T[] array, T value) { + int l = array.length; + + for (int i = 0; i < l; i++) { + if (array[i] == value) { return i; } + } + + return -1; + } + + public static int indexOf(T[] array, Predicate filter) { + int l = array.length; + + for (int i = 0; i < l; i++) { + if (filter.test(array[i])) { return i; } + } + + return -1; + } + + public static int indexOf(List array, Predicate filter) { + for (int i = 0, arraySize = array.size(); i < arraySize; i++) { + T value = array.get(i); + + if (filter.test(value)) { + return i; + } + } + + return -1; + } + + public static T find(T[] array, Predicate filter) { + for (T value : array) { + if (filter.test(value)) return value; + } + + return null; + } + + public static T find(Collection list, Predicate filter) { + for (T value : list) { + if (filter.test(value)) return value; + } + + return null; + } + + @SuppressWarnings("unchecked") + public static R findInstance(Collection list, Class clazz) { + for (T value : list) { + if (clazz.isInstance(value)) return (R) value; + } + + return null; + } + + public static int count(Collection list, Predicate filter) { + int count = 0; + + for (T value : list) { + if (filter.test(value)) count++; + } + + return count; + } + + public static ArrayList filterList(List input, Predicate filter) { + ArrayList output = new ArrayList<>(input.size()); + + for (int i = 0, inputSize = input.size(); i < inputSize; i++) { + T t = input.get(i); + + if (filter.test(t)) { + output.add(t); + } + } + + return output; + } + + public static T getIndexSafe(T[] array, int index) { + return array == null || index < 0 || index >= array.length ? null : array[index]; + } + + public static T getIndexSafe(List list, int index) { + return list == null || index < 0 || index >= list.size() ? null : list.get(index); + } + + public static T choose(List list, Random rng) { + if (list.isEmpty()) return null; + if (list.size() == 1) return list.get(0); + + return list.get(rng.nextInt(list.size())); + } + + public static boolean areMapsEqual(Map left, Map right) { + if (left == null || right == null) { + return left == right; + } + + HashSet keys = new HashSet<>(left.size() + right.size()); + + keys.addAll(left.keySet()); + keys.addAll(right.keySet()); + + for (K key : keys) { + if (!Objects.equals(left.get(key), right.get(key))) return false; + } + + return true; + } + + @SuppressWarnings("unchecked") + public static B transmute(A a) { + return (B) a; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static List transmuteList(List list) { + return (List) list; + } + + public static MethodHandle exposeFieldGetter(Class clazz, String... names) { + try { + Field field = ReflectionHelper.findField(clazz, names); + field.setAccessible(true); + return MethodHandles.lookup() + .unreflectGetter(field); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not make field getter for " + clazz.getName() + ":" + names[0], e); + } + } + + public static MethodHandle exposeFieldSetter(Class clazz, String... names) { + try { + Field field = ReflectionHelper.findField(clazz, names); + field.setAccessible(true); + return MethodHandles.lookup() + .unreflectSetter(field); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not make field setter for " + clazz.getName() + ":" + names[0], e); + } + } + + public static Function exposeFieldGetterLambda(Class clazz, String... names) { + final MethodHandle method = exposeFieldGetter(clazz, names); + + return instance -> { + try { + //noinspection unchecked + return (R) method.invokeExact(instance); + } catch (Throwable e) { + throw new RuntimeException("Could not get field " + clazz.getName() + ":" + names[0], e); + } + }; + } + + public static MethodHandle exposeMethod(Class clazz, MethodType sig, String... names) { + try { + Method method = ReflectionHelper.findMethod(clazz, null, names, sig.parameterArray()); + method.setAccessible(true); + return MethodHandles.lookup() + .unreflect(method); + } catch (IllegalAccessException e) { + throw new RuntimeException("Could not make method handle for " + clazz.getName() + ":" + names[0], e); + } + } + + public static > long enumSetToLong(EnumSet set) { + long data = 0; + + for (T t : set) { + data |= 1L << t.ordinal(); + } + + return data; + } + + public static > void longToEnumSet(Class clazz, long data, EnumSet set) { + set.clear(); + + for (T t : clazz.getEnumConstants()) { + if ((data & 1L << t.ordinal()) != 0) { + set.add(t); + } + } + } + + public static boolean toggleValue(Set set, T value) { + if (!set.remove(value)) { + set.add(value); + + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java index b6d3e2fa..575f6530 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java @@ -1,6 +1,7 @@ package com.cardinalstar.cubicchunks.world.worldgen.data; import java.util.EnumMap; +import java.util.List; import java.util.concurrent.TimeUnit; import net.minecraft.world.World; @@ -8,17 +9,18 @@ import org.jetbrains.annotations.NotNull; import com.cardinalstar.cubicchunks.async.TaskPool; -import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskExecutor; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskFuture; import com.cardinalstar.cubicchunks.util.ObjectPooler; import com.cardinalstar.cubicchunks.util.XSTR; import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; -import com.github.bsideup.jabel.Desugar; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalCause; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; - import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Data; +import lombok.EqualsAndHashCode; public class NoisePrecalculator & SamplerFactory> { @@ -39,11 +41,35 @@ public class NoisePrecalculator & SamplerFactory> { private final Class layers; private final int layerCount; private final int seed; + private final ITaskExecutor noiseTaskExecutor; public NoisePrecalculator(Class layers, int seed) { this.layers = layers; this.layerCount = layers.getEnumConstants().length; this.seed = seed; + + noiseTaskExecutor = new ITaskExecutor<>() { + + @Override + public void execute(List> tasks) { + for (var future : tasks) { + TaskKey task = future.getTask(); + + NoiseData data = getData(); + + compute3d(task.samplers, task.x << 4, task.y << 4, task.z << 4, data); + + cache.put(task, data); + + future.finish(data); + } + } + + @Override + public boolean canMerge(List> tasks, TaskKey taskKey) { + return tasks.size() < 32; + } + }; } private EnumMap createSamplers(long seed, String dimName) { @@ -88,15 +114,13 @@ public void releaseData(NoiseData data) { } public void submitPrecalculate(World world, int cubeX, int cubeY, int cubeZ) { - EnumMap samplers = getSamplers(world); + TaskKey task = new TaskKey(getSamplers(world), world.provider.dimensionId, cubeX, cubeY, cubeZ); - TaskKey key = new TaskKey(world.provider.dimensionId, cubeX, cubeY, cubeZ); - - TaskPool.submit(new Task3D(samplers, key)); + TaskPool.submit(this.noiseTaskExecutor, task); } public NoiseData takeSampler(World world, int cubeX, int cubeY, int cubeZ) { - TaskKey key = new TaskKey(world.provider.dimensionId, cubeX, cubeY, cubeZ); + TaskKey key = new TaskKey(null, world.provider.dimensionId, cubeX, cubeY, cubeZ); NoiseData data = cache.asMap() .remove(key); @@ -124,31 +148,15 @@ private void compute3d(EnumMap samplers, int wx, int wy, i }); } - private class Task3D implements ITask { + @Data + private class TaskKey { + @EqualsAndHashCode.Exclude private final EnumMap samplers; - private final TaskKey key; - - public Task3D(EnumMap samplers, TaskKey key) { - this.samplers = samplers; - this.key = key; - } - - @Override - public Void call() { - NoiseData data = getData(); - - compute3d(samplers, key.x() << 4, key.y() << 4, key.z() << 4, data); - - cache.put(key, data); - - return null; - } - } - - @Desugar - private record TaskKey(int worldId, int x, int y, int z) { - + private final int worldId; + private final int x; + private final int y; + private final int z; } public class NoiseData { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java index 116f4951..a2ed9b1e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -1,13 +1,14 @@ package com.cardinalstar.cubicchunks.world.worldgen.vanilla; -import java.util.Random; +import java.util.List; import java.util.concurrent.TimeUnit; import net.minecraft.world.gen.NoiseGeneratorOctaves; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.async.TaskPool; -import com.cardinalstar.cubicchunks.async.TaskPool.ITask; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskExecutor; +import com.cardinalstar.cubicchunks.async.TaskPool.ITaskFuture; import com.cardinalstar.cubicchunks.util.ObjectPooler; import com.github.bsideup.jabel.Desugar; import com.google.common.cache.Cache; @@ -15,6 +16,7 @@ public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements PrecalculableNoise { + private final NoiseGeneratorOctaves base; private boolean initialized = false; private int xspan, yspan, zspan; private double xscale, yscale, zscale; @@ -28,12 +30,36 @@ public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements Pr private final Cache cache = CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(1024) + .maximumSize(8192) .removalListener(notification -> releaseData((NoiseData) notification.getValue())) .build(); - public PrecalcedVanillaOctaves(Random rng, int octaves) { - super(rng, octaves); + private final ITaskExecutor noiseTaskExecutor; + + public PrecalcedVanillaOctaves(NoiseGeneratorOctaves base) { + super(null, 0); + this.base = base; + + noiseTaskExecutor = new ITaskExecutor<>() { + + @Override + public void execute(List> tasks) { + for (var future : tasks) { + Task3D task = future.getTask(); + + NoiseData data = task.run(); + + cache.put(task.key, data); + + future.finish(data); + } + } + + @Override + public boolean canMerge(List> tasks, Task3D task3D) { + return tasks.size() < 32; + } + }; } private static boolean diff(double a, double b) { @@ -105,7 +131,7 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int } misses2++; - return super.generateNoiseOctaves(data, blockX, blockY, blockZ, sx, sy, sz, scaleX, scaleY, scaleZ); + return base.generateNoiseOctaves(data, blockX, blockY, blockZ, sx, sy, sz, scaleX, scaleY, scaleZ); } private NoiseData getData() { @@ -134,7 +160,7 @@ public void precalculate(int blockX, int blockY, int blockZ) { task = new Task3D(key); } - TaskPool.submit(task); + TaskPool.submit(noiseTaskExecutor, task); } @Desugar @@ -143,7 +169,7 @@ private record TaskKey(int x, int y, int z) { } - private class Task3D implements ITask { + private class Task3D { private final TaskKey key; @@ -160,8 +186,7 @@ public Task3D(TaskKey key) { this.zscale = PrecalcedVanillaOctaves.this.zscale; } - @Override - public Void call() { + public NoiseData run() { NoiseData data = getData(); data.xspan = this.xspan; @@ -171,7 +196,7 @@ public Void call() { data.yscale = this.yscale; data.zscale = this.zscale; - PrecalcedVanillaOctaves.super.generateNoiseOctaves( + base.generateNoiseOctaves( data.data, key.x, key.y, @@ -183,9 +208,7 @@ public Void call() { yscale, zscale); - cache.put(key, data); - - return null; + return data; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index be853d9e..ff7b8fc3 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -73,7 +73,6 @@ import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.util.data.BlockMeta; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; - import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; @@ -213,7 +212,13 @@ private FillerInfo analyzeTopFiller(IBlockView blockView) { @Override public void onColumnPreloadFailed(ChunkCoordIntPair pos) { - // Do nothing, columns contain very little currently and don't need to be pre-generated + if (vanilla instanceof Precalculable precalc) { + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + precalc.precalculate(pos.chunkXPos + dx, 0, pos.chunkZPos + dz); + } + } + } } @Override @@ -475,8 +480,8 @@ public void populate(Cube cube) { new Vector3i(1, 1, 0), new Vector3i(0, 0, 1), new Vector3i(1, 0, 1), new Vector3i(0, 1, 1), new Vector3i(1, 1, 1), }; - private static final short[] CUBE_FLAGS = { Cube.POP_100, Cube.POP_010, Cube.POP_110, Cube.POP_001, Cube.POP_101, - Cube.POP_011, Cube.POP_111, }; +// private static final short[] CUBE_FLAGS = { Cube.POP_100, Cube.POP_010, Cube.POP_110, Cube.POP_001, Cube.POP_101, +// Cube.POP_011, Cube.POP_111, }; private Box getCubesToGenerate(int x, int y, int z) { if (y >= 0 && y < 16) { @@ -557,7 +562,11 @@ public void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel if (generate) { if (vanilla instanceof Precalculable precalc) { - precalc.precalculate(pos.getX(), pos.getY(), pos.getZ()); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + precalc.precalculate(pos.getX() + dx, 0, pos.getZ() + dz); + } + } } decorator.pregenerate(world, pos); From 2974efc8562b8423ce3e489c49b617c4c6da349e Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Thu, 20 Nov 2025 15:06:28 -0500 Subject: [PATCH 09/22] Several improvements --- .../cardinalstar/cubicchunks/api/ICube.java | 2 +- .../cubicchunks/api/MetaContainer.java | 8 + .../cardinalstar/cubicchunks/api/MetaKey.java | 5 + .../cardinalstar/cubicchunks/api/XYZMap.java | 8 +- .../cardinalstar/cubicchunks/api/XZMap.java | 12 +- .../cubicchunks/api/util/Box.java | 4 + .../mixin/early/common/MixinWorldServer.java | 19 +- .../cubicchunks/network/CCPacketBuffer.java | 18 + .../network/PacketEncoderCubeBlockChange.java | 19 +- .../network/PacketEncoderCubes.java | 3 + .../network/PacketEncoderHeightMapUpdate.java | 64 +- .../cubicchunks/server/ColumnWatcher.java | 242 ---- .../server/CubeProviderServer.java | 219 ++-- .../cubicchunks/server/CubeWatcher.java | 450 ------- .../server/CubicPlayerManager.java | 1091 +++++++++-------- .../server/chunkio/CCNBTUtils.java | 5 +- .../cubicchunks/server/chunkio/CubeIO.java | 57 +- .../server/chunkio/CubeLoaderServer.java | 67 +- .../server/chunkio/ICubeLoader.java | 13 +- .../server/chunkio/RegionCubeStorage.java | 2 +- .../cubicchunks/util/BlockPosSet.java | 69 ++ .../cubicchunks/util/BooleanArray2D.java | 88 ++ .../cubicchunks/util/BooleanArray3D.java | 9 +- .../cubicchunks/util/CubePos.java | 4 +- .../util/CubeStatusVisualizer.java | 65 + .../visibility/CuboidalCubeSelector.java | 4 + .../cubicchunks/world/CubeSpawnerAnimals.java | 80 +- .../world/chunkloader/CubicChunkManager.java | 85 +- .../cubicchunks/world/cube/Cube.java | 21 +- .../vanilla/PrecalcedVanillaOctaves.java | 44 +- .../worldgen/VanillaWorldGenerator.java | 13 +- 31 files changed, 1221 insertions(+), 1569 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/MetaContainer.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/MetaKey.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray2D.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java index 11efa04f..224814e8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java @@ -45,7 +45,7 @@ import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; @ParametersAreNonnullByDefault -public interface ICube extends XYZAddressable { +public interface ICube extends XYZAddressable, MetaContainer { /** * Side length of a cube diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/MetaContainer.java b/src/main/java/com/cardinalstar/cubicchunks/api/MetaContainer.java new file mode 100644 index 00000000..7d166c32 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/MetaContainer.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.api; + +public interface MetaContainer { + + T getMeta(MetaKey key); + + void setMeta(MetaKey key, T value); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/MetaKey.java b/src/main/java/com/cardinalstar/cubicchunks/api/MetaKey.java new file mode 100644 index 00000000..67c7ffc9 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/MetaKey.java @@ -0,0 +1,5 @@ +package com.cardinalstar.cubicchunks.api; + +public interface MetaKey { + +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java index 80216b97..b4b4d9f4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java @@ -54,12 +54,12 @@ public final T get(XYZAddressable xyz) { return get(xyz.getX(), xyz.getY(), xyz.getZ()); } - public final void put(T item) { - items.put(Coords.key(item.getX(), item.getY(), item.getZ()), item); + public final T put(T item) { + return items.put(Coords.key(item.getX(), item.getY(), item.getZ()), item); } - public final void remove(T item) { - remove(item.getX(), item.getY(), item.getZ()); + public final T remove(T2 key) { + return remove(key.getX(), key.getY(), key.getZ()); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/XZMap.java b/src/main/java/com/cardinalstar/cubicchunks/api/XZMap.java index 04520bd7..cad08854 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XZMap.java @@ -45,20 +45,20 @@ private long key(long x, long z) { return (x << 32) | (z & 0xFFFFFFFFL); } - public final void remove(int x, int z) { - items.remove(key(x, z)); + public final T remove(int x, int z) { + return items.remove(key(x, z)); } public final T get(int x, int z) { return items.get(key(x, z)); } - public final void put(T item) { - items.put(key(item.getX(), item.getZ()), item); + public final T put(T item) { + return items.put(key(item.getX(), item.getZ()), item); } - public final void remove(T item) { - remove(item.getX(), item.getZ()); + public final T remove(T item) { + return remove(item.getX(), item.getZ()); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java index acad1ced..8a94326c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/util/Box.java @@ -47,6 +47,10 @@ public Box(int x1, int y1, int z1, int x2, int y2, int z2) { this.z2 = Math.max(z1, z2); } + public Box(int x, int y, int z, int radius) { + this(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius); + } + public int getX1() { return x1; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java index bbc979b3..3c176a87 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java @@ -323,24 +323,17 @@ private void updateBlocksCubicChunks(CallbackInfo cbi) { // CubicChunks - iterate over PlayerCubeMap.TickableChunkContainer instead of Chunks, getTickableChunks already // includes forced chunks - CubicPlayerManager.TickableChunkContainer chunks = ((CubicPlayerManager) this.thePlayerManager) - .getTickableChunks(); - for (Chunk chunk : chunks.columns()) { + for (Chunk chunk : ((CubicPlayerManager) this.thePlayerManager).getColumnsToTick()) { tickColumn(raining, thundering, chunk); } + this.theProfiler.endStartSection("pollingCubes"); long worldTime = worldInfo.getWorldTotalTime(); - // CubicChunks - iterate over cubes instead of storage array from Chunk - for (ICube cube : chunks.forcedCubes()) { - tickCube(cube, worldTime); - } - for (ICube cube : chunks.playerTickableCubes()) { - if (cube == null) { // this is the internal array from the arraylist, anything beyond the size is null - break; - } - tickCube(cube, worldTime); - } + +// for (Cube cube : ((CubeProviderServer) this.theChunkProviderServer).getTickableCubes()) { +// tickCube(cube, worldTime); +// } this.theProfiler.endSection(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java index 350476c3..78a3128d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java @@ -139,6 +139,24 @@ public byte[] readByteArray() { return out; } + public void writeIntArray(int[] array) { + writeVarIntToBuffer(array.length); + + for (int x : array) { + writeVarIntToBuffer(x); + } + } + + public int[] readIntArray() { + final int[] out = new int[readVarIntFromBuffer()]; + + for (int i = 0; i < out.length; i++) { + out[i] = readVarIntFromBuffer(); + } + + return out; + } + public void writeList(List list, Encoder encoder) { writeVarIntToBuffer(list.size()); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index 0e3cc4cf..73e41cb4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -36,8 +36,6 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; - -import gnu.trove.TShortCollection; import gnu.trove.iterator.TIntIterator; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; @@ -57,21 +55,24 @@ public byte getPacketID() { public PacketEncoderCubeBlockChange() {} - public static PacketCubeBlockChange createPacket(Cube cube, TShortCollection localAddresses) { + public static PacketCubeBlockChange createPacket(Cube cube, short[] localAddresses) { CubePos cubePos = cube.getCoords(); - short[] localAddressArray = localAddresses.toArray(); - Block[] blocks = new Block[localAddressArray.length]; - int[] blockMetas = new int[localAddressArray.length]; + + Block[] blocks = new Block[localAddresses.length]; + int[] blockMetas = new int[localAddresses.length]; TIntSet xzAddresses = new TIntHashSet(); - for (int i = 0; i < localAddressArray.length; i++) { - short localAddress = localAddressArray[i]; + for (int i = 0, localAddressesLength = localAddresses.length; i < localAddressesLength; i++) { + short localAddress = localAddresses[i]; + int x = AddressTools.getLocalX(localAddress); int y = AddressTools.getLocalY(localAddress); int z = AddressTools.getLocalZ(localAddress); + blocks[i] = cube.getBlock(x, y, z); blockMetas[i] = cube.getBlockMetadata(x, y, z); + xzAddresses.add(AddressTools.getLocalAddress(x, z)); } @@ -89,7 +90,7 @@ public static PacketCubeBlockChange createPacket(Cube cube, TShortCollection loc i++; } - return new PacketCubeBlockChange(cubePos, localAddressArray, blocks, blockMetas, heightValues); + return new PacketCubeBlockChange(cubePos, localAddresses, blocks, blockMetas, heightValues); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index 03459d96..823dc9fd 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -35,6 +35,8 @@ import com.cardinalstar.cubicchunks.client.CubeProviderClient; import com.cardinalstar.cubicchunks.modcompat.angelica.AngelicaInterop; import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.CubeStatusVisualizer; +import com.cardinalstar.cubicchunks.util.CubeStatusVisualizer.CubeStatus; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; @@ -66,6 +68,7 @@ public static PacketCubes createPacket(List cubes) { for (int i = 0; i < cubes.size(); i++) { cubePos[i] = cubes.get(i) .getCoords(); + CubeStatusVisualizer.put(cubes.get(i).getCoords(), CubeStatus.Synced); } ByteBuf cubeData = Unpooled.buffer(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java index ce2d6ef7..c828fa6b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java @@ -20,8 +20,6 @@ */ package com.cardinalstar.cubicchunks.network; -import java.util.BitSet; - import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.world.ChunkCoordIntPair; @@ -29,26 +27,24 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.EmptyChunk; +import org.joml.Vector2ic; + import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.client.CubeProviderClient; import com.cardinalstar.cubicchunks.lighting.ILightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.util.AddressTools; +import com.cardinalstar.cubicchunks.util.BooleanArray2D; import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.github.bsideup.jabel.Desugar; - -import gnu.trove.list.TByteList; -import gnu.trove.list.TIntList; -import gnu.trove.list.array.TByteArrayList; -import gnu.trove.list.array.TIntArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; @ParametersAreNonnullByDefault public class PacketEncoderHeightMapUpdate extends CCPacketEncoder { @Desugar - public record PacketHeightMapUpdate(ChunkCoordIntPair chunk, TByteList updates, TIntList heights) + public record PacketHeightMapUpdate(ChunkCoordIntPair chunk, BooleanArray2D updates, IntArrayList heights) implements CCPacket { @Override @@ -59,18 +55,15 @@ public byte getPacketID() { public PacketEncoderHeightMapUpdate() {} - public static PacketHeightMapUpdate createPacket(BitSet updateSet, Chunk chunk) { + public static PacketHeightMapUpdate createPacket(BooleanArray2D updates, Chunk chunk) { ChunkCoordIntPair pos = chunk.getChunkCoordIntPair(); - TByteArrayList updates = new TByteArrayList(); - TIntArrayList heights = new TIntArrayList(); + IntArrayList heights = new IntArrayList(); - for (int i = updateSet.nextSetBit(0); i >= 0; i = updateSet.nextSetBit(i + 1)) { - updates.add((byte) i); - heights.add( - ((IColumnInternal) chunk).getTopYWithStaging(AddressTools.getLocalX(i), AddressTools.getLocalZ(i))); + for (Vector2ic v : updates) { + heights.add(((IColumnInternal) chunk).getTopYWithStaging(v.x(), v.y())); } - return new PacketHeightMapUpdate(pos, updates, heights); + return new PacketHeightMapUpdate(pos, updates.clone(), heights); } @Override @@ -82,30 +75,18 @@ public byte getPacketID() { public void writePacket(CCPacketBuffer buffer, PacketHeightMapUpdate packet) { buffer.writeChunkPos(packet.chunk); - int len = packet.updates.size(); - buffer.writeByte(len); - - for (int i = 0; i < len; i++) { - buffer.writeByte(packet.updates.get(i) & 0xFF); - buffer.writeVarIntToBuffer(packet.heights.get(i)); - } + buffer.writeByteArray(packet.updates.toByteArray()); + buffer.writeIntArray(packet.heights.toIntArray()); } @Override public PacketHeightMapUpdate readPacket(CCPacketBuffer buffer) { ChunkCoordIntPair pos = buffer.readChunkPos(); - int len = buffer.readByte(); - - TByteArrayList updates = new TByteArrayList(); - TIntArrayList heights = new TIntArrayList(); - - for (int i = 0; i < len; i++) { - updates.add(buffer.readByte()); - heights.add(buffer.readVarIntFromBuffer()); - } + BooleanArray2D updates = new BooleanArray2D(16, 16, buffer.readByteArray()); + int[] heights = buffer.readIntArray(); - return new PacketHeightMapUpdate(pos, updates, heights); + return new PacketHeightMapUpdate(pos, updates, IntArrayList.wrap(heights)); } @Override @@ -123,17 +104,14 @@ public void process(World world, PacketHeightMapUpdate packet) { ClientHeightMap index = (ClientHeightMap) ((IColumn) column).getOpacityIndex(); ILightingManager lm = worldClient.getLightingManager(); - int size = packet.updates.size(); + int i = 0; - for (int i = 0; i < size; i++) { - int packed = packet.updates.get(i) & 0xFF; - int x = AddressTools.getLocalX(packed); - int z = AddressTools.getLocalZ(packed); - int height = packet.heights.get(i); + for (Vector2ic v : packet.updates) { + int height = packet.heights.getInt(i++); - int oldHeight = index.getTopBlockY(x, z); - index.setHeight(x, z, height); - lm.updateLightBetween(column, x, oldHeight, height, z); + int oldHeight = index.getTopBlockY(v.x(), v.y()); + index.setHeight(v.x(), v.y(), height); + lm.updateLightBetween(column, v.x(), oldHeight, height, v.y()); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java b/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java deleted file mode 100644 index 01ce9aba..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.server; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; - -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.chunk.Chunk; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.world.ChunkWatchEvent; - -import com.cardinalstar.cubicchunks.CubicChunks; -import com.cardinalstar.cubicchunks.api.world.IColumnWatcher; -import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.network.PacketEncoderColumn; -import com.cardinalstar.cubicchunks.network.PacketEncoderHeightMapUpdate; -import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadColumn; -import com.cardinalstar.cubicchunks.util.AddressTools; -import com.cardinalstar.cubicchunks.util.BucketSorterEntry; -import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.XZAddressable; - -@ParametersAreNonnullByDefault -public class ColumnWatcher implements XZAddressable, BucketSorterEntry, IColumnWatcher { - - @Nonnull - private final CubicPlayerManager cubicPlayerManager; - private final CubeProviderServer cubeCache; - @Nonnull - private final BitSet dirtyColumns = new BitSet(256); - - private long previousWorldTime; - private final ChunkCoordIntPair pos; - @Nullable - private Chunk column; - private final List playersWatchingChunk = new ArrayList(); - - public boolean isSentToPlayers; - - public ColumnWatcher(CubicPlayerManager cubicPlayerManager, ChunkCoordIntPair pos) { - this.cubicPlayerManager = cubicPlayerManager; - this.pos = pos; - this.cubeCache = ((ICubicWorldInternal.Server) cubicPlayerManager.getWorldServer()).getCubeCache(); - - this.column = cubeCache.getLoadedColumn(pos.chunkXPos, pos.chunkZPos); - } - - public void onColumnLoaded(Chunk column) { - this.column = column; - } - - // CHECKED: 1.10.2-12.18.1.2092 - @Override - public void addPlayer(EntityPlayerMP player) { - if (playersWatchingChunk.contains(player)) { - CubicChunks.LOGGER - .debug("Failed to add player. {} already is in chunk {}, {}", player, pos.chunkXPos, pos.chunkZPos); - return; - } - - if (this.playersWatchingChunk.isEmpty()) { - this.previousWorldTime = cubicPlayerManager.getWorldServer() - .getTotalWorldTime(); - } - - playersWatchingChunk.add(player); - - // always sent to players, no need to check it - - if (this.isSentToPlayers) { - assert column != null; - PacketEncoderColumn.createPacket(column) - .sendToPlayer(player); - MinecraftForge.EVENT_BUS.post(new ChunkWatchEvent.Watch(column.getChunkCoordIntPair(), player)); - } - } - - // CHECKED: 1.10.2-12.18.1.2092//TODO: remove it, the only different line is sending packet - @Override - public void removePlayer(EntityPlayerMP player) { - if (!playersWatchingChunk.remove(player)) return; - - if (this.isSentToPlayers) { - PacketEncoderUnloadColumn.createPacket(pos.chunkXPos, pos.chunkZPos) - .sendToPlayer(player); - } - - if (column != null) { - MinecraftForge.EVENT_BUS.post(new ChunkWatchEvent.UnWatch(pos, player)); - } - - if (playersWatchingChunk.isEmpty()) { - cubicPlayerManager.removeEntry(this); - } - } - - @Override - public boolean containsPlayer(EntityPlayerMP playerMP) { - return playersWatchingChunk.contains(playerMP); - } - - @Override - public Chunk getColumn() { - return this.column; - } - - @Override - public ChunkCoordIntPair getPos() { - return pos; - } - - @Override - public void increaseInhabitedTime() { - if (column != null) { - this.column.inhabitedTime += cubicPlayerManager.getWorldServer() - .getTotalWorldTime() - this.previousWorldTime; - } - this.previousWorldTime = cubicPlayerManager.getWorldServer() - .getTotalWorldTime(); - } - - // providePlayerChunk - ok - - // CHECKED: 1.10.2-12.18.1.2092 - @Override - public boolean sendToPlayers() { - if (this.isSentToPlayers) return true; - if (this.column == null) return false; - - try { - var message = PacketEncoderColumn.createPacket(column); - for (EntityPlayerMP player : playersWatchingChunk) { - message.sendToPlayer(player); - } - isSentToPlayers = true; - } catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - - return true; - } - - @Override - @Deprecated - public void sendToPlayer(EntityPlayerMP player) { - // done by cube watcher - } - - @Override - @Deprecated - public void blockChanged(int x, int y, int z) { - CubeWatcher watcher = cubicPlayerManager.getCubeWatcher(CubePos.fromBlockCoords(x, y, z)); - - if (watcher != null) { - watcher.blockChanged(x, y, z); - } - } - - @Override - public void update() { - if (!isSentToPlayers) return; - - if (this.dirtyColumns.isEmpty()) return; - - assert this.column != null; - - var packet = PacketEncoderHeightMapUpdate.createPacket(dirtyColumns, this.column); - - for (EntityPlayerMP player : this.playersWatchingChunk) { - packet.sendToPlayer(player); - } - - this.dirtyColumns.clear(); - } - - // containsPlayer, hasPlayerMatching, hasPlayerMatchingInRange, isAddedToChunkUpdateQueue, getChunk, - // getClosestPlayerDistance - ok - - @Override - public int getX() { - return this.pos.chunkXPos; - } - - @Override - public int getZ() { - return this.pos.chunkZPos; - } - - public void heightChanged(int localX, int localZ) { - if (!isSentToPlayers) { - return; - } - assert this.column == cubicPlayerManager.getWorldServer() - .getChunkProvider() - .provideChunk(getX(), getZ()); - if (this.dirtyColumns.isEmpty()) { - cubicPlayerManager.addToUpdateEntry(this); - } - this.dirtyColumns.set(AddressTools.getLocalAddress(localX, localZ)); - } - - private long[] bucketDataEntry = null; - - @Override - public long[] getBucketEntryData() { - return bucketDataEntry; - } - - @Override - public void setBucketEntryData(long[] data) { - bucketDataEntry = data; - } - - public List getWatchingPlayers() { - return playersWatchingChunk; - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index b7c95f94..3472c92b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -26,8 +26,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.function.BooleanSupplier; @@ -37,7 +39,7 @@ import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.entity.EnumCreatureType; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.profiler.Profiler; import net.minecraft.util.IProgressUpdate; import net.minecraft.world.ChunkCoordIntPair; @@ -46,7 +48,6 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.IChunkLoader; import net.minecraft.world.gen.ChunkProviderServer; -import net.minecraftforge.common.ForgeChunkManager; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; @@ -57,20 +58,22 @@ import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderCallback; import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderServer; import com.cardinalstar.cubicchunks.server.chunkio.ICubeLoader; -import com.cardinalstar.cubicchunks.util.BucketSorterEntry; import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.WatchersSortingList3D; +import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; import com.cardinalstar.cubicchunks.world.column.EmptyColumn; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal; import com.cardinalstar.cubicchunks.world.savedata.WorldFormatSavedData; -import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; - +import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; +import it.unimi.dsi.fastutil.ints.IntComparator; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import lombok.Getter; +import lombok.Setter; /** * This is CubicChunks equivalent of ChunkProviderServer, it loads and unloads Cubes and Columns. @@ -101,9 +104,8 @@ public class CubeProviderServer extends ChunkProviderServer @Nonnull private final Profiler profiler; - private final WatchersSortingList3D eagerCubeLoads = new WatchersSortingList3D<>( - 0, - this::getPlayers); + private final Map eagerLoads = new Object2ObjectOpenHashMap<>(); + private final List eagerLoadOrder = new ArrayList<>(); private int columnsLoadedThisTick = 0; private int cubesLoadedThisTick = 0; @@ -199,13 +201,32 @@ public void onCubeUnloaded(Cube cube) { } } - private List getPlayers() { + private int getChunkDistanceSquared(ChunkCoordIntPair coord) { + int min = Integer.MAX_VALUE; + + List players = getPlayers(); + + for (int i = 0, playersSize = players.size(); i < playersSize; i++) { + EntityPlayerMP player = players.get(i); + final int dX = player.chunkCoordX - coord.chunkXPos; + final int dZ = player.chunkCoordZ - coord.chunkZPos; + + final int dist2 = dX * dX + dZ * dZ; + + if (dist2 < min) min = dist2; + } + + return min; + } + + private List getPlayers() { // worldServer == null when this provider is being constructed because the field isn't set before the sorting // lists call this method. // noinspection ConstantValue if (worldServer == null) return Collections.emptyList(); - return worldServer.playerEntities; + //noinspection unchecked + return (List) (List) worldServer.playerEntities; } @Override @@ -296,20 +317,16 @@ public void tick() { Random rand = this.worldObj.rand; BooleanSupplier tickFaster = () -> System.currentTimeMillis() - start > 40; - profiler.startSection("Tick force loaded cubes"); + profiler.startSection("Tick cubes"); - for (Cube cube : getForceLoadedCubes()) { - cube.tickCubeServer(tickFaster, rand); - } - - profiler.endStartSection("Tick watched cubes"); - - for (Cube cube : ((CubicPlayerManager) worldServer.getPlayerManager()).getWatchedCubes()) { + for (Cube cube : getTickableCubes()) { cube.tickCubeServer(tickFaster, rand); } profiler.endSection(); + getCubeLoader().setNow(worldObj.getTotalWorldTime()); + doEagerLoading(); columnsLoadedThisTick = 0; @@ -319,73 +336,81 @@ public void tick() { private void doEagerLoading() { profiler.startSection("Eager object sorting"); - eagerCubeLoads.tick(); + // TODO: make this faster + eagerLoadOrder.sort(Comparator.comparingInt(this::getChunkDistanceSquared).reversed()); profiler.endStartSection("Eager object loading"); long start = System.nanoTime(); - Iterator cubeIter = eagerCubeLoads.iterator(); + while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && !eagerLoadOrder.isEmpty()) { + ChunkCoordIntPair coord = eagerLoadOrder.get(eagerLoadOrder.size() - 1); - while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { - EagerCubeLoadRequest request = cubeIter.next(); - cubeIter.remove(); - request.completed = true; + EagerCubeLoadContainer container = eagerLoads.get(coord); - cubeLoader.pauseLoadCalls(); + if (container == null) { + eagerLoads.remove(coord); + eagerLoadOrder.remove(eagerLoadOrder.size() - 1); + continue; + } - Cube cube = cubeLoader.getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); + Iterator cubeIter = container.cubes.values().iterator(); - cubeLoader.unpauseLoadCalls(); + while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { + EagerCubeLoadRequest request = cubeIter.next(); - CubeInitLevel actual = cube == null ? CubeInitLevel.None : cube.getInitLevel(); - CubeInitLevel wanted = CubeInitLevel.fromRequirement(request.effort); + cubeIter.remove(); + request.completed = true; - if (actual.ordinal() < wanted.ordinal()) { - CubicChunks.LOGGER.error( - "Could not init cube {},{},{} for eager request (wanted {}, returned {})", - request.pos.getX(), - request.pos.getY(), - request.pos.getZ(), - wanted, - actual); - } - } + if (request.isCancelled()) continue; - long delta = System.nanoTime() - start; - - if (delta > MAX_NS_SPENT_LOADING * 2) { - CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); - } + cubeLoader.preloadCube(request.pos, CubeInitLevel.fromRequirement(request.effort)); - profiler.endSection(); - } + try { + Thread.sleep(1); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } - private List getForceLoadedCubes() { - ImmutableSetMultimap persistentChunks = ForgeChunkManager - .getPersistentChunksFor(worldServer); + cubeLoader.pauseLoadCalls(); - worldServer.theProfiler.startSection("forcedChunkLoading"); + Cube cube = cubeLoader.getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); - ArrayList loadedCubes = new ArrayList<>(); + cubeLoader.unpauseLoadCalls(); - for (ChunkCoordIntPair pos : persistentChunks.keySet()) { - @SuppressWarnings("unchecked") - Collection cubes = (Collection) ((IColumn) worldServer - .getChunkFromChunkCoords(pos.chunkXPos, pos.chunkZPos)).getLoadedCubes(); + CubeInitLevel actual = cube == null ? CubeInitLevel.None : cube.getInitLevel(); + CubeInitLevel wanted = CubeInitLevel.fromRequirement(request.effort); - for (Cube cube : cubes) { - if (cube == null) continue; - if (cube.isEmpty()) continue; - if (!cube.isPopulated()) continue; + if (actual.ordinal() < wanted.ordinal()) { + CubicChunks.LOGGER.error( + "Could not init cube {},{},{} for eager request (wanted {}, returned {})", + request.pos.getX(), + request.pos.getY(), + request.pos.getZ(), + wanted, + actual); + } + } - loadedCubes.add(cube); + if (container.cubes.isEmpty()) { + eagerLoads.remove(coord); + eagerLoadOrder.remove(eagerLoadOrder.size() - 1); + } else { + break; } } - worldServer.theProfiler.endSection(); + long delta = System.nanoTime() - start; + + if (delta > MAX_NS_SPENT_LOADING * 2) { + CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); + } - return loadedCubes; + profiler.endSection(); + } + + public Collection getTickableCubes() { + return ((CubicPlayerManager) worldServer.getPlayerManager()).getCubes(); } @Override @@ -431,32 +456,48 @@ public Cube getLoadedCube(CubePos coords) { return getLoadedCube(coords.getX(), coords.getY(), coords.getZ()); } - @SuppressWarnings("unused") - public class EagerCubeLoadRequest implements XYZAddressable, BucketSorterEntry { + public static class EagerCubeLoadContainer implements XZAddressable { - public final CubePos pos; - private Requirement effort; - private boolean completed; + public final ChunkCoordIntPair pos; - public EagerCubeLoadRequest(CubePos pos, Requirement effort) { + public final Int2ObjectRBTreeMap cubes = new Int2ObjectRBTreeMap<>(IntComparator.comparingInt(i -> -i)); + + public EagerCubeLoadContainer(ChunkCoordIntPair pos) { this.pos = pos; - this.effort = effort; } - public Requirement getEffort() { - return effort; + public void add(EagerCubeLoadRequest request) { + cubes.put(request.getY(), request); } - public void setEffort(Requirement effort) { - this.effort = effort; + @Override + public int getX() { + return pos.chunkXPos; + } + + @Override + public int getZ() { + return pos.chunkZPos; } + } + + @SuppressWarnings("unused") + public static class EagerCubeLoadRequest implements XYZAddressable { - public boolean isCompleted() { - return completed; + public final CubePos pos; + @Setter + @Getter + private Requirement effort; + @Getter + private boolean completed, cancelled; + + public EagerCubeLoadRequest(CubePos pos, Requirement effort) { + this.pos = pos; + this.effort = effort; } public void cancel() { - CubeProviderServer.this.eagerCubeLoads.remove(this); + this.cancelled = true; } @Override @@ -476,18 +517,6 @@ public String toString() { return "EagerCubeLoadRequest{" + "pos=" + pos + '}'; } - private long[] bucketDataEntry = null; - - @Override - public long[] getBucketEntryData() { - return bucketDataEntry; - } - - @Override - public void setBucketEntryData(long[] data) { - bucketDataEntry = data; - } - @Override public int getX() { return pos.getX(); @@ -507,9 +536,19 @@ public int getZ() { public EagerCubeLoadRequest loadCubeEagerly(int x, int y, int z, Requirement effort) { CubePos pos = new CubePos(x, y, z); + ChunkCoordIntPair coord = new ChunkCoordIntPair(x, z); + + EagerCubeLoadContainer container = eagerLoads.get(coord); + + if (container == null) { + container = new EagerCubeLoadContainer(coord); + eagerLoads.put(coord, container); + eagerLoadOrder.add(coord); + } + EagerCubeLoadRequest request = new EagerCubeLoadRequest(pos, effort); - eagerCubeLoads.add(request); + container.add(request); cubeLoader.preloadCube(pos, CubeInitLevel.fromRequirement(effort)); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java deleted file mode 100644 index 8b704a0b..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). - * Copyright (c) 2015-2021 OpenCubicChunks - * Copyright (c) 2015-2021 contributors - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cardinalstar.cubicchunks.server; - -import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; - -import net.minecraft.block.Block; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.network.Packet; -import net.minecraft.tileentity.TileEntity; -import net.minecraft.world.World; -import net.minecraftforge.common.ForgeModContainer; -import net.minecraftforge.common.MinecraftForge; - -import com.cardinalstar.cubicchunks.CubicChunks; -import com.cardinalstar.cubicchunks.api.world.ICubeWatcher; -import com.cardinalstar.cubicchunks.entity.ICubicEntityTracker; -import com.cardinalstar.cubicchunks.event.events.CubeUnWatchEvent; -import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.network.PacketEncoderCubeBlockChange; -import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadCube; -import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; -import com.cardinalstar.cubicchunks.util.AddressTools; -import com.cardinalstar.cubicchunks.util.BucketSorterEntry; -import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.ITicket; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; -import com.cardinalstar.cubicchunks.world.cube.Cube; -import com.google.common.base.Predicate; -import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; - -import gnu.trove.list.TShortList; -import gnu.trove.list.array.TShortArrayList; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -@ParametersAreNonnullByDefault -public class CubeWatcher implements ITicket, ICubeWatcher, BucketSorterEntry { - - private final CubeProviderServer cubeCache; - private final CubicPlayerManager cubicPlayerManager; - @Nullable - private Cube cube; - // Note: using wrap() so that the internal array is not Object[], and can be safely cast to EntityPlayerMP[] - private final ObjectArrayList players = ObjectArrayList.wrap(new EntityPlayerMP[0]); - private final ObjectArrayList playersToAdd = ObjectArrayList.wrap(new EntityPlayerMP[1], 0); - - private final TShortList dirtyBlocks = new TShortArrayList(8); - private final CubePos cubePos; - private long previousWorldTime = 0; - private boolean sentToPlayers = false; - private boolean invalid = false; - private CubeProviderServer.EagerCubeLoadRequest request; - - // CHECKED: 1.10.2-12.18.1.2092 - CubeWatcher(CubicPlayerManager cubicPlayerManager, CubePos cubePos) { - this.cubePos = cubePos; - this.cubicPlayerManager = cubicPlayerManager; - this.cubeCache = ((ICubicWorldInternal.Server) cubicPlayerManager.getWorldServer()).getCubeCache(); - - Cube loaded = cubeCache.getLoadedCube(cubePos); - - if (loaded != null && loaded.isInitializedToLevel(CubeInitLevel.Lit)) { - onCubeLoaded(loaded); - } else { - request = cubeCache - .loadCubeEagerly(cubePos.getX(), cubePos.getY(), cubePos.getZ(), ICubeProviderServer.Requirement.LIGHT); - } - } - - public void onCubeLoaded(Cube c) { - if (this.invalid) return; - if (c.getInitLevel() != CubeInitLevel.Lit) { - if (request == null || request.isCompleted()) { - request = cubeCache.loadCubeEagerly( - cubePos.getX(), - cubePos.getY(), - cubePos.getZ(), - ICubeProviderServer.Requirement.LIGHT); - } - - return; - } - - if (this.cube != c) { - if (this.cube != null) this.cube.getTickets().remove(this); - - this.cube = c; - this.cube.getTickets() - .add(this); - } - - if (this.request != null) this.request.cancel(); - this.request = null; - } - - public void scheduleAddPlayer(EntityPlayerMP player) { - if (!playersToAdd.contains(player)) { - playersToAdd.add(player); - } - } - - public void addScheduledPlayers() { - if (!playersToAdd.isEmpty()) { - for (EntityPlayerMP player : playersToAdd.elements()) { - if (player == null) { - break; - } - addPlayer(player); - } - playersToAdd.clear(); - } - } - - // CHECKED: 1.10.2-12.18.1.2092 - private void addPlayer(EntityPlayerMP player) { - if (this.players.contains(player)) { - CubicChunks.LOGGER.debug("Failed to add player. {} already is in cube at {}", player, cubePos); - return; - } - if (this.players.isEmpty()) { - this.previousWorldTime = this.getWorldTime(); - } - this.players.add(player); - - if (this.sentToPlayers) { - this.sendToPlayer(player); - ((ICubicEntityTracker) cubicPlayerManager.getWorldServer() - .getEntityTracker()).sendLeashedEntitiesInCube(player, this.getCube()); - } - } - - // CHECKED: 1.10.2-12.18.1.2092 - void removePlayer(EntityPlayerMP player) { - if (!this.players.contains(player)) { - playersToAdd.remove(player); - if (this.players.isEmpty()) { - cubicPlayerManager.removeEntry(this); - } - return; - } - - // If we haven't loaded yet don't load the chunk just so we can clean it up - if (this.cube == null) { - this.players.remove(player); - - if (this.players.isEmpty()) { - cubicPlayerManager.removeEntry(this); - } - return; - } - - if (this.sentToPlayers) { - PacketEncoderUnloadCube.createPacket(cubePos) - .sendToPlayer(player); - cubicPlayerManager.removeSchedulesSendCubeToPlayer(cube, player); - } - - this.players.remove(player); - MinecraftForge.EVENT_BUS.post(new CubeUnWatchEvent(cube, cubePos, this, player)); - - if (this.players.isEmpty()) { - cubicPlayerManager.removeEntry(this); - if (this.request != null) this.request.cancel(); - } - } - - public void invalidate() { - if (request != null) request.cancel(); - invalid = true; - playersToAdd.clear(); - } - - @Override - public boolean isSentToPlayers() { - return sentToPlayers; - } - - public boolean isWaitingForCube() { - return this.cube == null || this.cube.getInitLevel() != CubeInitLevel.Lit; - } - - public boolean isWaitingForColumn() { - ColumnWatcher columnEntry = cubicPlayerManager.getColumnWatcher(this.cubePos.chunkPos()); - return columnEntry == null || !columnEntry.isSentToPlayers; - } - - // CHECKED: 1.10.2-12.18.1.2092 - public boolean sendToPlayers() { - if (this.sentToPlayers) return true; - - if (isWaitingForCube()) return false; - - // can't send cubes before columns - if (isWaitingForColumn()) return false; - - this.dirtyBlocks.clear(); - - // set to true before adding to queue so that sendToPlayer can actually add it - this.sentToPlayers = true; - - for (EntityPlayerMP playerEntry : this.players) { - sendToPlayer(playerEntry); - } - - return true; - } - - // CHECKED: 1.10.2-12.18.1.2092 - private void sendToPlayer(EntityPlayerMP player) { - if (!this.sentToPlayers) { - return; - } - assert cube != null; - cubicPlayerManager.scheduleSendCubeToPlayer(cube, player); - } - - // CHECKED: 1.10.2-12.18.1.2092 - public void updateInhabitedTime() { - final long now = getWorldTime(); - if (this.cube == null) { - this.previousWorldTime = now; - return; - } - - long inhabitedTime = this.cube.getColumn().inhabitedTime; - inhabitedTime += now - this.previousWorldTime; - - this.cube.getColumn().inhabitedTime = inhabitedTime; - this.previousWorldTime = now; - } - - // CHECKED: 1.10.2-12.18.1.2092 - void blockChanged(int localX, int localY, int localZ) { - // if we are adding the first one, add it to update list - if (this.dirtyBlocks.isEmpty()) { - cubicPlayerManager.addToUpdateEntry(this); - } - // If the number of changes is above clumpingThreshold - // we send the whole cube, but to decrease network usage - // forge sends only TEs that have changed, - // so we need to know all changed blocks. So add everything - // it's a set so no need to check for duplicates - this.dirtyBlocks.add((short) AddressTools.getLocalAddress(localX, localY, localZ)); - } - - // CHECKED: 1.10.2-12.18.1.2092 - void update() { - if (!this.sentToPlayers) { - return; - } - assert cube != null; - // are there any updates? - if (this.dirtyBlocks.isEmpty()) { - return; - } - - World world = this.cube.getWorld(); - - if (!this.players.isEmpty()) { - if (this.dirtyBlocks.size() >= ForgeModContainer.clumpingThreshold) { - // send whole cube - for (EntityPlayerMP entry : this.players) { - cubicPlayerManager.scheduleSendCubeToPlayer(cube, entry); - } - } else { - // send all the dirty blocks - var packet = PacketEncoderCubeBlockChange.createPacket(this.cube, this.dirtyBlocks); - for (EntityPlayerMP player : this.players) { - packet.sendToPlayer(player); - } - // send the block entities on those blocks too - this.dirtyBlocks.forEach(localAddress -> { - BlockPos pos = cube.localAddressToBlockPos(localAddress); - - Block block = this.cube.getBlock(pos.getX(), pos.getY(), pos.getZ()); - int meta = this.cube.getBlockMetadata(pos.getX(), pos.getY(), pos.getZ()); - if (block.hasTileEntity(meta)) { - sendBlockEntityToAllPlayers(world.getTileEntity(pos.getX(), pos.getY(), pos.getZ())); - } - return true; - }); - } - } - - this.dirtyBlocks.clear(); - } - - private void sendBlockEntityToAllPlayers(@Nullable TileEntity blockEntity) { - if (blockEntity == null) { - return; - } - Packet packet = blockEntity.getDescriptionPacket(); - if (packet == null) { - return; - } - sendPacketToAllPlayers(packet); - } - - public boolean containsPlayer(EntityPlayerMP player) { - return this.players.contains(player); - } - - boolean hasPlayerMatching(Predicate predicate) { - for (EntityPlayerMP e : players.elements()) { - if (e == null) { - break; - } - if (predicate.apply(e)) { - return true; - } - } - return false; - } - - public boolean hasPlayer() { - return !players.isEmpty(); - } - - boolean hasPlayerMatchingInRange(Predicate predicate, int range) { - double d = range * range; - double cx = cubePos.getXCenter(); - double cy = cubePos.getYCenter(); - double cz = cubePos.getZCenter(); - for (EntityPlayerMP e : players.elements()) { - if (e == null) { - break; - } - if (predicate.apply(e)) { - double dist = cx - e.posX; - dist *= dist; - if (dist > d) { - continue; - } - double dy = cy - e.posY; - dist += dy * dy; - if (dist > d) { - continue; - } - double dz = cz - e.posZ; - dist += dz * dz; - if (dist > d) { - continue; - } - return true; - } - } - return false; - } - - private double getDistanceSq(CubePos cubePos, Entity entity) { - double blockX = cubePos.getXCenter(); - double blockY = cubePos.getYCenter(); - double blockZ = cubePos.getZCenter(); - double dx = blockX - entity.posX; - double dy = blockY - entity.posY; - double dz = blockZ - entity.posZ; - return dx * dx + dy * dy + dz * dz; - } - - private double getClosestPlayerDistance() { - double min = Double.MAX_VALUE; - - for (EntityPlayerMP entry : this.players.elements()) { - if (entry == null) { - break; - } - double dist = getDistanceSq(cubePos, entry); - - if (dist < min) { - min = dist; - } - } - - return min; - } - - private long getWorldTime() { - return cubicPlayerManager.getWorldServer() - .getWorldTime(); - } - - private void sendPacketToAllPlayers(Packet packet) { - for (EntityPlayerMP entry : this.players) { - entry.playerNetServerHandler.sendPacket(packet); - } - } - - @Override - @Nullable - public Cube getCube() { - return this.cube; - } - - @Override - public CubePos getCubePos() { - return cubePos; - } - - @Override - public int getX() { - return this.cubePos.getX(); - } - - @Override - public int getY() { - return this.cubePos.getY(); - } - - @Override - public int getZ() { - return this.cubePos.getZ(); - } - - @Override - public boolean shouldTick() { - return false; // player seeing a cube is not enough to force ticking from the ticket system - } - - private long[] bucketDataEntry = null; - - @Override - public long[] getBucketEntryData() { - return bucketDataEntry; - } - - @Override - public void setBucketEntryData(long[] data) { - bucketDataEntry = data; - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index 115a3eda..f7e1567f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -21,59 +21,68 @@ package com.cardinalstar.cubicchunks.server; import static com.cardinalstar.cubicchunks.util.Coords.blockToCube; -import static com.cardinalstar.cubicchunks.util.Coords.blockToLocal; import static net.minecraft.util.MathHelper.clamp_int; +import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.List; import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.Packet; import net.minecraft.server.management.PlayerManager; +import net.minecraft.tileentity.TileEntity; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.WorldProvider; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.gen.ChunkProviderServer; -import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.ForgeModContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.joml.Vector3ic; import com.cardinalstar.cubicchunks.CubicChunks; -import com.cardinalstar.cubicchunks.CubicChunksConfig; -import com.cardinalstar.cubicchunks.api.IColumn; -import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.XYZMap; import com.cardinalstar.cubicchunks.api.XZMap; import com.cardinalstar.cubicchunks.api.util.Box; -import com.cardinalstar.cubicchunks.api.world.CubeWatchEvent; -import com.cardinalstar.cubicchunks.entity.ICubicEntityTracker; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal.Server; +import com.cardinalstar.cubicchunks.network.PacketEncoderColumn; +import com.cardinalstar.cubicchunks.network.PacketEncoderCubeBlockChange; import com.cardinalstar.cubicchunks.network.PacketEncoderCubes; +import com.cardinalstar.cubicchunks.network.PacketEncoderHeightMapUpdate; +import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadColumn; +import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadColumn.PacketUnloadColumn; +import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadCube; +import com.cardinalstar.cubicchunks.network.PacketEncoderUnloadCube.PacketUnloadCube; +import com.cardinalstar.cubicchunks.server.CubeProviderServer.EagerCubeLoadRequest; import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.server.chunkio.CubeLoaderCallback; +import com.cardinalstar.cubicchunks.util.AddressTools; +import com.cardinalstar.cubicchunks.util.BooleanArray2D; +import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; -import com.cardinalstar.cubicchunks.util.WatchersSortingList2D; -import com.cardinalstar.cubicchunks.util.WatchersSortingList3D; -import com.cardinalstar.cubicchunks.visibility.CubeSelector; +import com.cardinalstar.cubicchunks.util.CubeStatusVisualizer; +import com.cardinalstar.cubicchunks.util.CubeStatusVisualizer.CubeStatus; +import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.visibility.CuboidalCubeSelector; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.google.common.collect.AbstractIterator; -import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.SetMultimap; +import com.google.common.collect.Iterators; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; /** * A cubic chunks implementation of Player Manager. @@ -83,99 +92,22 @@ @ParametersAreNonnullByDefault public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallback { - /** - * Cube selector is used to find which cube positions need to be loaded/unloaded - * By default use CuboidalCubeSelector. - */ - private final CubeSelector cubeSelector = new CuboidalCubeSelector(); - /** * Mapping if entityId to PlayerCubeMap.PlayerWrapper objects. */ - private final Int2ObjectOpenHashMap players = new Int2ObjectOpenHashMap<>(); - - /** - * Mapping of Cube positions to CubeWatchers (Cube equivalent of PlayerManager.PlayerInstance). - * Contains cube positions of all cubes loaded by players. - */ - final XYZMap cubeWatchers = new XYZMap<>(); - - /** - * Mapping of Column positions to ColumnWatchers. - * Contains column positions of all columns loaded by players. - * Exists for compatibility with vanilla and to send ColumnLoad/Unload packets to clients. - * Columns cannot be managed by client because they have separate data, like heightmap and biome array. - */ - final XZMap columnWatchers = new XZMap<>(); + private final Int2ObjectOpenHashMap players = new Int2ObjectOpenHashMap<>(); - /** - * All cubeWatchers that have pending block updates to send. - */ - private final Set cubeWatchersToUpdate = new HashSet<>(); + private final CubeProviderServer provider; - /** - * All columnWatchers that have pending height updates to send. - */ - private final Set columnWatchersToUpdate = new HashSet<>(); + private final XYZMap watchedCubes = new XYZMap<>(); + private final Set dirtyCubes = new ObjectOpenHashSet<>(); - /** - * A queue of cubes to add a player to, this limits the amount of cubes sent to a player per tick to the set limit - * even when joining an area with already existing cube watchers - */ - private final WatchersSortingList3D watchersToAddPlayersTo = new WatchersSortingList3D<>( - 0, - () -> players.values() - .stream() - .map(p -> p.playerEntity) - .collect(Collectors.toList())); - - /** - * Contains all CubeWatchers that need to be sent to clients, - * but these cubes are not fully loaded/generated yet. - *

- * Note that this is not the same as cubesToGenerate list. - * Cube can be loaded while not being fully generated yet (not in the last GeneratorStageRegistry stage). - */ - private final WatchersSortingList3D cubesToSendToClients = new WatchersSortingList3D<>( - 1, - () -> players.values() - .stream() - .map(p -> p.playerEntity) - .collect(Collectors.toList())); - - /** - * Contains all ColumnWatchers that need to be sent to clients, - * but these cubes are not fully loaded/generated yet. - *

- * Note that this is not the same as columnsToGenerate list. - * Columns can be loaded while not being fully generated yet - */ - private final WatchersSortingList2D columnsToSendToClients = new WatchersSortingList2D<>( - 3, - () -> players.values() - .stream() - .map(p -> p.playerEntity) - .collect(Collectors.toList())); - - private final WatchersSortingList3D tickableCubeTracker = new WatchersSortingList3D<>( - 5, - () -> players.values() - .stream() - .map(p -> p.playerEntity) - .collect(Collectors.toList())); + private final XZMap watchedColumns = new XZMap<>(); + private final Set dirtyColumns = new ObjectOpenHashSet<>(); private int horizontalViewDistance; private int verticalViewDistance; - /** - * This is used only to force update of all CubeWatchers every 8000 ticks - */ - private long previousWorldTime = 0; - - private final SetMultimap cubesToSend = MultimapBuilder.hashKeys() - .hashSetValues() - .build(); - // these player adds will be processed on the next tick // this exists as temporary workaround to player respawn code calling addPlayer() before spawning // the player in world as it's spawning player in world that triggers sending cubic chunks world @@ -183,11 +115,7 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallb // knows it's a cubic chunks world delaying addPlayer() by one tick fixes it. // this should be fixed by hooking into the code in a different place to send the cubic chunks world information // (player respawn packet?) - private Set pendingPlayerAddToCubeMap = new HashSet<>(); - - private final TickableChunkContainer tickableChunksCubesToReturn = new TickableChunkContainer(); - - // final VanillaNetworkHandler vanillaNetworkHandler; + private final Set pendingPlayerAddToCubeMap = new HashSet<>(); public CubicPlayerManager(WorldServer worldServer) { super(worldServer); @@ -197,72 +125,70 @@ public CubicPlayerManager(WorldServer worldServer) { .getViewDistance(), ((ICubicPlayerList) worldServer.func_73046_m() .getConfigurationManager()).getVerticalViewDistance()); - // this.vanillaNetworkHandler = ((ICubicWorldInternal.Server) worldServer).getVanillaNetworkHandler(); - ((ICubicWorldInternal.Server) worldServer).getCubeCache() - .registerCallback(this); - } - - // /** - // * This method exists only because vanilla needs it. It shouldn't be used anywhere else. - // */ - // @Override - // @Deprecated // Warning: Hacks! For vanilla use only! (WorldServer.updateBlocks()) - // public Iterator getChunkIterator() { - // // CubicChunks.bigWarning("Usage of PlayerCubeMap#getChunkIterator detected in a cubic chunks world! " - // // + "This is likely to work incorrectly. This is not supported."); - // // TODO: throw UnsupportedOperationException? - // Iterator chunkIt = this.cubeCache.getLoadedChunks().iterator(); - // return new AbstractIterator() { - // @Override protected Chunk computeNext() { - // while (chunkIt.hasNext()) { - // IColumn column = (IColumn) chunkIt.next(); - // if (column.shouldTick()) { // shouldTick is true when there Cubes with tickets the request to be ticked - // return (Chunk) column; - // } - // } - // return this.endOfData(); - // } - // }; - // } - public TickableChunkContainer getTickableChunks() { - TickableChunkContainer tickableChunksCubes = this.tickableChunksCubesToReturn; - tickableChunksCubes.clear(); - addTickableColumns(tickableChunksCubes); - addTickableCubes(tickableChunksCubes); - addForcedColumns(tickableChunksCubes); - addForcedCubes(tickableChunksCubes); - return tickableChunksCubes; + provider = ((Server) worldServer).getCubeCache(); + provider.registerCallback(this); } - private void addForcedColumns(TickableChunkContainer tickableChunksCubes) { - for (IColumn columns : ((ICubicWorldInternal.Server) getWorldServer()).getForcedColumns()) { - tickableChunksCubes.addColumn((Chunk) columns); - } + public Iterable getColumnsToTick() { + // TODO: this + return Collections.emptyList(); } - private void addForcedCubes(TickableChunkContainer tickableChunksCubes) { - tickableChunksCubes.forcedCubes = ((ICubicWorldInternal.Server) getWorldServer()).getForcedCubes(); + public Iterable getCubesToTick() { + // TODO: this + return Collections.emptyList(); } - private void addTickableCubes(TickableChunkContainer tickableChunksCubes) { - for (CubeWatcher watcher : (Iterable) () -> tickableCubeTracker.iteratorUpToDistance(9)) { - ICube cube = watcher.getCube(); - if (cube == null) { - continue; + public Iterable getWatchedColumns() { + return () -> new AbstractIterator<>() { + + final Iterator iter = watchedColumns.iterator(); + + @Override + protected Chunk computeNext() { + while (iter.hasNext()) { + WatchedColumn column = iter.next(); + + if (column.column == null || column.watchingPlayers.isEmpty()) continue; + + return column.column; + } + + return this.endOfData(); } - tickableChunksCubes.addCube(cube); - } + }; } - private void addTickableColumns(TickableChunkContainer tickableChunksCubes) { - for (ColumnWatcher watcher : columnWatchers) { - Chunk chunk = watcher.getColumn(); - if (chunk == null) { // TODO WATCH IF YOU NEED TO CHECK RANGE - continue; + public Collection getCubes() { + return new AbstractCollection<>() { + + @Override + public @NotNull Iterator iterator() { + return new AbstractIterator<>() { + + final Iterator iter = watchedCubes.iterator(); + + @Override + protected Cube computeNext() { + while (iter.hasNext()) { + WatchedCube cube = iter.next(); + + if (cube.cube == null) continue; + + return cube.cube; + } + + return this.endOfData(); + } + }; } - tickableChunksCubes.addColumn(chunk); - } + + @Override + public int size() { + return watchedCubes.getSize(); + } + }; } /** @@ -274,177 +200,176 @@ private void addTickableColumns(TickableChunkContainer tickableChunksCubes) { public void updatePlayerInstances() { getWorldServer().theProfiler.startSection("playerCubeMapUpdatePlayerInstances"); - long currentTime = this.getWorldServer() - .getTotalWorldTime(); - getWorldServer().theProfiler.startSection("addPendingPlayers"); - if (!pendingPlayerAddToCubeMap.isEmpty()) { - // copy in case player still isn't in world - Set players = pendingPlayerAddToCubeMap; - pendingPlayerAddToCubeMap = new HashSet<>(); - for (EntityPlayerMP player : players) { + + for (EntityPlayerMP player : pendingPlayerAddToCubeMap) { + if (player.addedToChunk) { addPlayer(player); } } + + pendingPlayerAddToCubeMap.removeIf(e -> e.addedToChunk); + + syncColumns(); + syncCubes(); + getWorldServer().theProfiler.endStartSection("tickEntries"); - // force update-all every 8000 ticks (400 seconds) - if (currentTime - this.previousWorldTime > 8000L) { - this.previousWorldTime = currentTime; - for (CubeWatcher playerInstance : this.cubeWatchers) { - playerInstance.update(); - playerInstance.updateInhabitedTime(); - } - } + getWorldServer().theProfiler.endStartSection("unload"); - // process instances to update - if (!cubeWatchersToUpdate.isEmpty()) { - this.cubeWatchersToUpdate.forEach(CubeWatcher::update); - this.cubeWatchersToUpdate.clear(); - } + // if there are no players - unload everything + if (this.players.isEmpty()) { + WorldProvider worldprovider = this.getWorldServer().provider; - if (!columnWatchersToUpdate.isEmpty()) { - this.columnWatchersToUpdate.forEach(ColumnWatcher::update); - this.columnWatchersToUpdate.clear(); + if (!worldprovider.canRespawnHere()) { + provider.unloadAllChunks(); + } } - getWorldServer().theProfiler.endStartSection("sortTickableTracker"); - tickableCubeTracker.tick(); - getWorldServer().theProfiler.endStartSection("sortToSend"); - this.cubesToSendToClients.tick(); - this.columnsToSendToClients.tick(); - this.watchersToAddPlayersTo.tick(); - - getWorldServer().theProfiler.endStartSection("send"); + getWorldServer().theProfiler.endSection();// sendCubes + getWorldServer().theProfiler.endSection();// playerCubeMapUpdatePlayerInstances + } - if (!this.columnsToSendToClients.isEmpty()) { - getWorldServer().theProfiler.startSection("columns"); + private long lastWorldTime; - this.columnsToSendToClients.removeIf(ColumnWatcher::sendToPlayers); + private void syncColumns() { + long now = getWorldServer().getWorldTime(); - getWorldServer().theProfiler.endSection(); // columns - } + long delta = lastWorldTime == 0 ? 0 : now - lastWorldTime; - if (!this.cubesToSendToClients.isEmpty()) { - getWorldServer().theProfiler.startSection("cubes"); + this.lastWorldTime = now; - Iterator iter = this.cubesToSendToClients.iterator(); - int toSend = CubicChunksConfig.cubesToSendPerTick; + for (WatchedColumn column : dirtyColumns) { + if (column.column == null) continue; - while (iter.hasNext() && toSend > 0) { - CubeWatcher cubeWatcher = iter.next(); + column.column.inhabitedTime += delta; - if (cubeWatcher.sendToPlayers()) { - iter.remove(); - toSend--; + switch (column.dirty) { + case None -> { + // whar? + } + case Partial -> { + column.syncPartial(); + } + case Full -> { + column.syncFull(); } } - getWorldServer().theProfiler.endSection(); // cubes + column.clean(); } - if (!watchersToAddPlayersTo.isEmpty()) { - Iterator iter = watchersToAddPlayersTo.iterator(); - int toSend = CubicChunksConfig.cubesToSendPerTick; + dirtyColumns.clear(); + } + + private void syncCubes() { + ((ICubicWorldInternal) getWorldServer()).getLightingManager().onSendCubes(() -> Iterators.transform(dirtyCubes.iterator(), c -> c.cube)); - while (toSend > 0 && iter.hasNext()) { - CubeWatcher cubeWatcher = iter.next(); - cubeWatcher.addScheduledPlayers(); + List fullSync = new ObjectArrayList<>(); - if (cubeWatcher.sendToPlayers()) { - iter.remove(); - toSend--; + for (WatchedCube cube : dirtyCubes) { + if (cube.cube == null) continue; + + switch (cube.dirty) { + case None -> { + // huh? + } + case Partial -> { + cube.syncPartial(); + cube.clean(); + } + case Full -> { + fullSync.add(cube); } } + + cube.dirty = Dirtiness.None; } - getWorldServer().theProfiler.endStartSection("unload"); - // if there are no players - unload everything - if (this.players.isEmpty()) { - WorldProvider worldprovider = this.getWorldServer().provider; + dirtyCubes.clear(); - if (!worldprovider.canRespawnHere()) { - ((ChunkProviderServer) this.getWorldServer() - .getChunkProvider()).unloadAllChunks(); + for (WatchedCube cube : fullSync) { + for (WatchingPlayer player : cube.watchingPlayers) { + player.queueCube(cube.cube); } + + cube.clean(); } - getWorldServer().theProfiler.endStartSection("sendCubes");// unload - if (!cubesToSend.isEmpty()) { - for (EntityPlayerMP player : cubesToSend.keySet()) { - Collection cubes = cubesToSend.get(player); - if (!players.containsKey(player.getEntityId())) { - CubicChunks.LOGGER.info( - "Skipping sending " + cubes.size() - + " chunks to player " - + player.getCommandSenderName() - + " that is no longer in this world!"); - continue; - } - ((ICubicWorldInternal) getWorldServer()).getLightingManager() - .onSendCubes(cubes); - // if (vanillaNetworkHandler.hasCubicChunks(player)) { - ArrayList list = new ArrayList<>(100); - for (Cube cube : cubes) { - list.add(cube); - if (list.size() >= 100) { - PacketEncoderCubes.createPacket(list) - .sendToPlayer(player); - list.clear(); - } - } - if (!list.isEmpty()) { - PacketEncoderCubes.createPacket(list) - .sendToPlayer(player); - } - // } else { - // vanillaNetworkHandler.sendCubeLoadPackets(cubes, player); - // } - // Sending entities per cube. - for (Cube cube : cubes) { - ((ICubicEntityTracker) getWorldServer().getEntityTracker()).sendLeashedEntitiesInCube(player, cube); - CubeWatcher watcher = getCubeWatcher(cube.getCoords()); - assert watcher != null; - MinecraftForge.EVENT_BUS.post(new CubeWatchEvent(cube, cube.getCoords(), watcher, player)); - } - } - cubesToSend.clear(); + + for (WatchingPlayer player : this.players.values()) { + player.flushCubes(); } - getWorldServer().theProfiler.endSection();// sendCubes - getWorldServer().theProfiler.endSection();// playerCubeMapUpdatePlayerInstances } @Override public void onColumnLoaded(Chunk column) { - ColumnWatcher watcher = this.columnWatchers.get(column.xPosition, column.zPosition); + WatchedColumn watcher = this.watchedColumns.get(column.xPosition, column.zPosition); if (watcher != null) { - watcher.onColumnLoaded(column); + watcher.setColumn(column); } } @Override - public void onCubeLoaded(Cube cube) { - CubeWatcher watcher = this.cubeWatchers.get(cube.getX(), cube.getY(), cube.getZ()); + public void onColumnUnloaded(Chunk column) { + WatchedColumn watcher = this.watchedColumns.remove(column.xPosition, column.zPosition); if (watcher != null) { - watcher.onCubeLoaded(cube); + PacketUnloadColumn packet = PacketEncoderUnloadColumn.createPacket(column.xPosition, column.zPosition); + + for (WatchingPlayer player : watcher.watchingPlayers) { + packet.sendToPlayer(player.player); + } } } + @Override + public void onCubeLoaded(Cube cube) { + WatchedCube watcher = this.getOrCreateCubeWatcher(cube.getCoords()); + + watcher.setCube(cube); + + CubeStatusVisualizer.put(cube.getCoords(), switch (cube.getInitLevel()) { + case None -> CubeStatus.None; + case Generated -> CubeStatus.Generated; + case Populated -> CubeStatus.Populated; + case Lit -> CubeStatus.Lit; + }); + } + @Override public void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { - CubeWatcher watcher = this.cubeWatchers.get(cube); + WatchedCube watcher = this.getOrCreateCubeWatcher(cube.getCoords()); - if (watcher != null) { - watcher.onCubeLoaded(cube); + watcher.setCube(cube); + + CubeStatusVisualizer.put(cube.getCoords(), switch (cube.getInitLevel()) { + case None -> CubeStatus.None; + case Generated -> CubeStatus.Generated; + case Populated -> CubeStatus.Populated; + case Lit -> CubeStatus.Lit; + }); + } + + @Override + public void onCubeUnloaded(Cube cube) { + WatchedCube watcher = this.watchedCubes.remove(cube); + + if (watcher != null && watcher.cube != null) { + PacketUnloadCube packet = PacketEncoderUnloadCube.createPacket(cube.getCoords()); + + for (WatchingPlayer player : watcher.watchingPlayers) { + packet.sendToPlayer(player.player); + } } + + CubeStatusVisualizer.remove(cube.getCoords()); } // CHECKED: 1.10.2-12.18.1.2092 - // contains(int cubeX, int cubeZ) @Override - public boolean func_152621_a(int cubeX, int cubeZ) { - return this.columnWatchers.get(cubeX, cubeZ) != null; + public boolean func_152621_a(int columnX, int columnZ) { + return isColumnWatched(columnX, columnZ); } // TODO WATCH @@ -459,24 +384,13 @@ public boolean func_152621_a(int cubeX, int cubeZ) { * Attempts to load the cube and send it to client. * If it can't load it or send it to client - adds it to cubesToGenerate/cubesToSendToClients */ - private CubeWatcher getOrCreateCubeWatcher(CubePos cubePos) { - CubeWatcher cubeWatcher = this.cubeWatchers.get(cubePos.getX(), cubePos.getY(), cubePos.getZ()); + private WatchedCube getOrCreateCubeWatcher(CubePos cubePos) { + WatchedCube cubeWatcher = this.watchedCubes.get(cubePos.getX(), cubePos.getY(), cubePos.getZ()); if (cubeWatcher == null) { - // make a new watcher - cubeWatcher = new CubeWatcher(this, cubePos); - this.cubeWatchers.put(cubeWatcher); - this.tickableCubeTracker.add(cubeWatcher); - - // vanilla has the below check, which causes the cubes to be sent to client too early and sometimes in too - // big amounts - // if they are sent too early, client won't have the right player position and renderer positions are wrong - // which cause some cubes to not be rendered - // DO NOT make it the same as vanilla until it's confirmed that Mojang fixed MC-120079 - // if (!cubeWatcher.sendToPlayers()) { - this.cubesToSendToClients.add(cubeWatcher); - // } + this.watchedCubes.put(cubeWatcher = new WatchedCube(cubePos.getX(), cubePos.getY(), cubePos.getZ())); } + return cubeWatcher; } @@ -484,39 +398,31 @@ private CubeWatcher getOrCreateCubeWatcher(CubePos cubePos) { * Returns existing ColumnWatcher or creates new one if it doesn't exist. * Always creates the Column. */ - private ColumnWatcher getOrCreateColumnWatcher(ChunkCoordIntPair chunkPos) { - ColumnWatcher columnWatcher = this.columnWatchers.get(chunkPos.chunkXPos, chunkPos.chunkZPos); + private WatchedColumn getOrCreateWatchedColumn(ChunkCoordIntPair chunkPos) { + WatchedColumn watchedColumn = this.watchedColumns.get(chunkPos.chunkXPos, chunkPos.chunkZPos); - if (columnWatcher == null) { - this.columnWatchers.put(columnWatcher = new ColumnWatcher(this, chunkPos)); - - if (!columnWatcher.sendToPlayers()) { - this.columnsToSendToClients.add(columnWatcher); - } + if (watchedColumn == null) { + this.watchedColumns.put(watchedColumn = new WatchedColumn(chunkPos.chunkXPos, chunkPos.chunkZPos)); } - return columnWatcher; + return watchedColumn; } // CHECKED: 1.10.2-12.18.1.2092 @Override public void markBlockForUpdate(int x, int y, int z) { - CubeWatcher cubeWatcher = this.getCubeWatcher(x >> 4, y >> 4, z >> 4); + WatchedCube cube = watchedCubes.get(x >> 4, y >> 4, z >> 4); - if (cubeWatcher != null) { - int localX = blockToLocal(x); - int localY = blockToLocal(y); - int localZ = blockToLocal(z); - cubeWatcher.blockChanged(localX, localY, localZ); + if (cube != null) { + cube.markDirty(x, y, z); } } - public void heightUpdated(int blockX, int blockZ) { - ColumnWatcher columnWatcher = this.columnWatchers.get(blockToCube(blockX), blockToCube(blockZ)); - if (columnWatcher != null) { - int localX = blockToLocal(blockX); - int localZ = blockToLocal(blockZ); - columnWatcher.heightChanged(localX, localZ); + public void heightUpdated(int x, int z) { + WatchedColumn column = watchedColumns.get(x >> 4, z >> 4); + + if (column != null) { + column.markDirty(x, z); } } @@ -537,70 +443,63 @@ public void addPlayer(EntityPlayerMP player) { return; } - PlayerWrapper playerWrapper = new PlayerWrapper(player); - playerWrapper.updateManagedPos(); - - // if (!vanillaNetworkHandler.hasCubicChunks(player)) { - // vanillaNetworkHandler.updatePlayerPosition(this, player, playerWrapper.getManagedCubePos()); - // } + WatchingPlayer watchingPlayer = new WatchingPlayer(player); + watchingPlayer.updateManagedPos(); CubePos playerCubePos = CubePos.fromEntity(player); - this.cubeSelector - .forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (currentPos) -> { - // create cubeWatcher and chunkWatcher - // order is important - ColumnWatcher chunkWatcher = getOrCreateColumnWatcher(currentPos.chunkPos()); - // and add the player to them - if (!chunkWatcher.containsPlayer(player)) { - chunkWatcher.addPlayer(player); - } - CubeWatcher cubeWatcher = getOrCreateCubeWatcher(currentPos); + CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (currentPos) -> { + // create cubeWatcher and chunkWatcher + // order is important - scheduleAddPlayerToWatcher(cubeWatcher, player); - }); - this.players.put(player.getEntityId(), playerWrapper); + getOrCreateWatchedColumn(currentPos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(currentPos).addPlayer(watchingPlayer); + }); + + watchingPlayer.flushCubes(); + + this.players.put(player.getEntityId(), watchingPlayer); } // CHECKED: 1.10.2-12.18.1.2092 @Override public void removePlayer(EntityPlayerMP player) { - PlayerWrapper playerWrapper = this.players.get(player.getEntityId()); - if (playerWrapper == null) { + WatchingPlayer watchingPlayer = this.players.remove(player.getEntityId()); + + if (watchingPlayer == null) { return; } + // Minecraft does something evil there: this method is called *after* changing the player's position // so we need to use managedPosition there CubePos playerCubePos = CubePos - .fromEntityCoords(player.managedPosX, playerWrapper.managedPosY, player.managedPosZ); + .fromEntityCoords(player.managedPosX, watchingPlayer.managedPosY, player.managedPosZ); // send unload columns later so that they get unloaded after their corresponding cubes - ObjectSet toSendUnload = new ObjectOpenHashSet<>( - (horizontalViewDistance * 2 + 1) * (horizontalViewDistance * 2 + 1) * 6); - this.cubeSelector.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> { + ObjectSet unloadedColumns = new ObjectOpenHashSet<>( + (horizontalViewDistance * 2 + 1) * (horizontalViewDistance * 2 + 1)); + CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> { // get the watcher - CubeWatcher watcher = getCubeWatcher(cubePos); - if (watcher != null) { - // remove from the watcher, it also removes the watcher if it becomes empty - removePlayerFromCubeWatcher(watcher, player); + WatchedCube cube = watchedCubes.get(cubePos); + + if (cube != null) { + // Cube will be GC'd if it isn't watched by a player + cube.removePlayer(watchingPlayer); } // remove column watchers if needed - ColumnWatcher columnWatcher = getColumnWatcher(cubePos.chunkPos()); - if (columnWatcher == null) { - return; - } + WatchedColumn column = watchedColumns.get(cubePos.getX(), cubePos.getZ()); - toSendUnload.add(columnWatcher); - }); - for (ColumnWatcher watcher : toSendUnload) { - if (watcher.containsPlayer(player)) { - watcher.removePlayer(player); + if (column != null) { + // Column will be GC'd if it isn't watched by a player + unloadedColumns.add(column); } + }); + + for (WatchedColumn watcher : unloadedColumns) { + watcher.removePlayer(watchingPlayer); } - this.players.remove(player.getEntityId()); - // vanillaNetworkHandler.removePlayer(player); } // CHECKED: 1.10.2-12.18.1.2092 @@ -611,23 +510,22 @@ public void updatePlayerPertinentChunks(EntityPlayerMP player) { // then update the list of chunks that need to be sent to the client // get the player info - PlayerWrapper playerWrapper = this.players.get(player.getEntityId()); + WatchingPlayer watchingPlayer = this.players.get(player.getEntityId()); - if (playerWrapper == null) { + if (watchingPlayer == null) { // vanilla sometimes does it, this is normal return; } + // did the player move into new cube? - if (!playerWrapper.cubePosChanged()) { + if (!watchingPlayer.cubePosChanged()) { return; } - this.updatePlayer(playerWrapper, playerWrapper.getManagedCubePos(), CubePos.fromEntity(player)); - playerWrapper.updateManagedPos(); + this.updatePlayer(watchingPlayer, watchingPlayer.getManagedCubePos(), CubePos.fromEntity(player)); + + watchingPlayer.updateManagedPos(); - // if (!vanillaNetworkHandler.hasCubicChunks(player)) { - // vanillaNetworkHandler.updatePlayerPosition(this, player, playerWrapper.getManagedCubePos()); - // } // With ChunkGc being separate from PlayerCubeMap, there are 2 issues: // Problem 0: Sometimes, a chunk can be generated after CubeWatcher's chunk load callback returns with a null // but before ChunkGC call. This means that the cube will get unloaded, even when ChunkWatcher is waiting for @@ -649,16 +547,18 @@ public void updatePlayerPertinentChunks(EntityPlayerMP player) { .doGC(); } - private void updatePlayer(PlayerWrapper entry, CubePos oldPos, CubePos newPos) { + private void updatePlayer(WatchingPlayer player, CubePos oldPos, CubePos newPos) { getWorldServer().theProfiler.startSection("updateMovedPlayer"); + Set cubesToRemove = new HashSet<>(); Set cubesToLoad = new HashSet<>(); Set columnsToRemove = new HashSet<>(); Set columnsToLoad = new HashSet<>(); getWorldServer().theProfiler.startSection("findChanges"); + // calculate new visibility - this.cubeSelector.findChanged( + CuboidalCubeSelector.INSTANCE.findChanged( oldPos, newPos, horizontalViewDistance, @@ -668,42 +568,39 @@ private void updatePlayer(PlayerWrapper entry, CubePos oldPos, CubePos newPos) { columnsToRemove, columnsToLoad); - getWorldServer().theProfiler.endStartSection("createColumns"); // order is important, columns first + + getWorldServer().theProfiler.endStartSection("createColumns"); columnsToLoad.forEach(pos -> { - ColumnWatcher columnWatcher = this.getOrCreateColumnWatcher(pos); - assert columnWatcher.getPos() - .equals(pos); - columnWatcher.addPlayer(entry.playerEntity); + this.getOrCreateWatchedColumn(pos).addPlayer(player); }); + getWorldServer().theProfiler.endStartSection("createCubes"); cubesToLoad.forEach(pos -> { - CubeWatcher cubeWatcher = this.getOrCreateCubeWatcher(pos); - assert cubeWatcher.getCubePos() - .equals(pos); - scheduleAddPlayerToWatcher(cubeWatcher, entry.playerEntity); + this.getOrCreateCubeWatcher(pos).addPlayer(player); }); + getWorldServer().theProfiler.endStartSection("removeCubes"); cubesToRemove.forEach(pos -> { - CubeWatcher cubeWatcher = this.getCubeWatcher(pos); - if (cubeWatcher != null) { - assert cubeWatcher.getCubePos() - .equals(pos); - removePlayerFromCubeWatcher(cubeWatcher, entry.playerEntity); + WatchedCube cube = watchedCubes.get(pos); + + if (cube != null) { + cube.removePlayer(player); } }); + getWorldServer().theProfiler.endStartSection("removeColumns"); columnsToRemove.forEach(pos -> { - ColumnWatcher columnWatcher = this.getColumnWatcher(pos); - if (columnWatcher != null) { - assert columnWatcher.getPos() - .equals(pos); - columnWatcher.removePlayer(entry.playerEntity); + WatchedColumn column = watchedColumns.get(pos.chunkXPos, pos.chunkZPos); + + if (column != null) { + column.removePlayer(player); } }); + getWorldServer().theProfiler.endStartSection("Immediate nearby cube loading"); - CubeProviderServer cubeCache = ((ICubicWorldInternal.Server) getWorldServer()).getCubeCache(); + CubeProviderServer cubeCache = ((Server) getWorldServer()).getCubeCache(); // Force load the cube the player is in along with its 26 neighbours for (Vector3ic v : new Box(-1, -1, -1, 1, 1, 1)) { @@ -711,32 +608,55 @@ private void updatePlayer(PlayerWrapper entry, CubePos oldPos, CubePos newPos) { newPos.getX() + v.x(), newPos.getY() + v.y(), newPos.getZ() + v.z(), - ICubeProviderServer.Requirement.LIGHT); + Requirement.LIGHT); } getWorldServer().theProfiler.endSection();// Immediate nearby cube loading getWorldServer().theProfiler.endSection();// updateMovedPlayer } - private void removePlayerFromCubeWatcher(CubeWatcher cubeWatcher, EntityPlayerMP playerEntity) { - cubeWatcher.removePlayer(playerEntity); + public boolean isColumnWatched(int columnX, int columnZ) { + WatchedColumn column = watchedColumns.get(columnX, columnZ); + + return column != null && !column.watchingPlayers.isEmpty(); } - private void scheduleAddPlayerToWatcher(CubeWatcher cubeWatcher, EntityPlayerMP playerEntity) { - watchersToAddPlayersTo.add(cubeWatcher); - cubeWatcher.scheduleAddPlayer(playerEntity); + public boolean isCubeWatched(int cubeX, int cubeY, int cubeZ) { + WatchedCube cube = watchedCubes.get(cubeX, cubeY, cubeZ); + + return cube != null && !cube.watchingPlayers.isEmpty(); + } + + public boolean isCubeWatchedAndPresent(int cubeX, int cubeY, int cubeZ) { + WatchedCube cube = watchedCubes.get(cubeX, cubeY, cubeZ); + + return cube != null && cube.cube != null && !cube.watchingPlayers.isEmpty(); } // CHECKED: 1.10.2-12.18.1.2092 @Override public boolean isPlayerWatchingChunk(EntityPlayerMP player, int cubeX, int cubeZ) { - ColumnWatcher columnWatcher = this.getColumnWatcher(new ChunkCoordIntPair(cubeX, cubeZ)); - return columnWatcher != null && columnWatcher.containsPlayer(player) && columnWatcher.isSentToPlayers; + WatchedColumn column = watchedColumns.get(cubeX, cubeZ); + + if (column == null || column.column == null) return false; + + for (WatchingPlayer watchingPlayer : column.watchingPlayers) { + if (watchingPlayer.player == player) return true; + } + + return false; } public boolean isPlayerWatchingCube(EntityPlayerMP player, int cubeX, int cubeY, int cubeZ) { - CubeWatcher watcher = this.getCubeWatcher(new CubePos(cubeX, cubeY, cubeZ)); - return watcher != null && watcher.containsPlayer(player) && watcher.isSentToPlayers(); + WatchedCube cube = watchedCubes.get(cubeX, cubeY, cubeZ); + + if (cube == null || cube.cube == null) return false; + + for (WatchingPlayer watchingPlayer : cube.watchingPlayers) { + if (watchingPlayer.player == player) return true; + } + + return false; } // CHECKED: 1.10.2-12.18.1.2092 @@ -774,32 +694,23 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe return; } - for (PlayerWrapper playerWrapper : this.players.values()) { + for (WatchingPlayer watchingPlayer : this.players.values()) { - EntityPlayerMP player = playerWrapper.playerEntity; - CubePos playerPos = playerWrapper.getManagedCubePos(); + EntityPlayerMP player = watchingPlayer.player; + CubePos playerPos = watchingPlayer.getManagedCubePos(); if (newHorizontalViewDistance > oldHorizontalViewDistance || newVerticalViewDistance > oldVerticalViewDistance) { // if newRadius is bigger, we only need to load new cubes - this.cubeSelector - .forAllVisibleFrom(playerPos, newHorizontalViewDistance, newVerticalViewDistance, pos -> { - // order is important - ColumnWatcher columnWatcher = this.getOrCreateColumnWatcher(pos.chunkPos()); - if (!columnWatcher.containsPlayer(player)) { - columnWatcher.addPlayer(player); - } - CubeWatcher cubeWatcher = this.getOrCreateCubeWatcher(pos); - if (!cubeWatcher.containsPlayer(player)) { - scheduleAddPlayerToWatcher(cubeWatcher, player); - } - }); - // either both got smaller or only one of them changed + CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerPos, newHorizontalViewDistance, newVerticalViewDistance, pos -> { + getOrCreateWatchedColumn(pos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(pos).addPlayer(watchingPlayer); + }); } else { - // if it got smaller... + // either both got smaller or only one of them changed Set cubesToUnload = new HashSet<>(); Set columnsToUnload = new HashSet<>(); - this.cubeSelector.findAllUnloadedOnViewDistanceDecrease( + CuboidalCubeSelector.INSTANCE.findAllUnloadedOnViewDistanceDecrease( playerPos, oldHorizontalViewDistance, newHorizontalViewDistance, @@ -809,20 +720,14 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe columnsToUnload); cubesToUnload.forEach(pos -> { - CubeWatcher cubeWatcher = this.getCubeWatcher(pos); - if (cubeWatcher != null) { - removePlayerFromCubeWatcher(cubeWatcher, player); - } else { - CubicChunks.LOGGER.warn("cubeWatcher null on render distance change"); - } + WatchedCube cube = watchedCubes.get(pos); + + if (cube != null) cube.removePlayer(watchingPlayer); }); columnsToUnload.forEach(pos -> { - ColumnWatcher columnWatcher = this.getColumnWatcher(pos); - if (columnWatcher != null && columnWatcher.containsPlayer(player)) { - columnWatcher.removePlayer(player); - } else { - CubicChunks.LOGGER.warn("cubeWatcher null or doesn't contain player on render distance change"); - } + WatchedColumn column = watchedColumns.get(pos.chunkXPos, pos.chunkZPos); + + if (column != null) column.removePlayer(watchingPlayer); }); } } @@ -831,95 +736,39 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe this.verticalViewDistance = newVerticalViewDistance; } - // TODO NEEDED? - // @Override - // public void entryChanged(IColumnWatcher entry) { - // throw new UnsupportedOperationException(); - // } - // - // @Override - // public void removeEntry(IColumnWatcher entry) { - // throw new UnsupportedOperationException(); - // } + private static class WatchingPlayer { - void addToUpdateEntry(CubeWatcher cubeWatcher) { - this.cubeWatchersToUpdate.add(cubeWatcher); - } - - void addToUpdateEntry(ColumnWatcher columnWatcher) { - this.columnWatchersToUpdate.add(columnWatcher); - } + public final EntityPlayerMP player; + private double managedPosY; + private final ArrayList cubeSendQueue = new ArrayList<>(20); - // CHECKED: 1.10.2-12.18.1.2092 - void removeEntry(CubeWatcher cubeWatcher) { - watchersToAddPlayersTo.remove(cubeWatcher); - cubeWatcher.invalidate(); - cubeWatcher.updateInhabitedTime(); - this.tickableCubeTracker.remove(cubeWatcher); - CubeWatcher removed = this.cubeWatchers.remove(cubeWatcher.getX(), cubeWatcher.getY(), cubeWatcher.getZ()); - assert removed == cubeWatcher : "Removed unexpected cube watcher"; - this.cubeWatchersToUpdate.remove(cubeWatcher); - this.cubesToSendToClients.remove(cubeWatcher); - if (cubeWatcher.getCube() != null) { - cubeWatcher.getCube() - .getTickets() - .remove(cubeWatcher); // remove the ticket, so this Cube can unload + WatchingPlayer(EntityPlayerMP player) { + this.player = player; } - // don't unload, ChunkGc unloads chunks - } - - public void removeEntry(ColumnWatcher entry) { - ChunkCoordIntPair pos = entry.getPos(); - entry.increaseInhabitedTime(); - this.columnWatchers.remove(pos.chunkXPos, pos.chunkZPos); - this.columnsToSendToClients.remove(entry); - this.columnWatchersToUpdate.remove(entry); - } - - public void scheduleSendCubeToPlayer(Cube cube, EntityPlayerMP player) { - cubesToSend.put(player, cube); - } - - public void removeSchedulesSendCubeToPlayer(Cube cube, EntityPlayerMP player) { - cubesToSend.remove(player, cube); - } - - @Nullable - public CubeWatcher getCubeWatcher(int x, int y, int z) { - return this.cubeWatchers.get(x, y, z); - } - - @Nullable - public CubeWatcher getCubeWatcher(CubePos pos) { - return this.cubeWatchers.get(pos.getX(), pos.getY(), pos.getZ()); - } - - @Nullable - public ColumnWatcher getColumnWatcher(ChunkCoordIntPair pos) { - return this.columnWatchers.get(pos.chunkXPos, pos.chunkZPos); - } - - public boolean contains(CubePos coords) { - return this.cubeWatchers.get(coords.getX(), coords.getY(), coords.getZ()) != null; - } - private static final class PlayerWrapper { + public void queueCube(Cube cube) { + cubeSendQueue.add(cube); - final EntityPlayerMP playerEntity; - private double managedPosY; + if (cubeSendQueue.size() >= 20) { + flushCubes(); + } + } - PlayerWrapper(EntityPlayerMP player) { - this.playerEntity = player; + public void flushCubes() { + if (!cubeSendQueue.isEmpty()) { + PacketEncoderCubes.createPacket(cubeSendQueue).sendToPlayer(player); + cubeSendQueue.clear(); + } } void updateManagedPos() { - this.playerEntity.managedPosX = playerEntity.posX; - this.managedPosY = playerEntity.posY; - this.playerEntity.managedPosZ = playerEntity.posZ; + this.player.managedPosX = player.posX; + this.managedPosY = player.posY; + this.player.managedPosZ = player.posZ; } int getManagedCubePosX() { - return blockToCube(this.playerEntity.managedPosX); + return blockToCube(this.player.managedPosX); } int getManagedCubePosY() { @@ -927,7 +776,7 @@ int getManagedCubePosY() { } int getManagedCubePosZ() { - return blockToCube(this.playerEntity.managedPosZ); + return blockToCube(this.player.managedPosZ); } CubePos getManagedCubePos() { @@ -936,66 +785,232 @@ CubePos getManagedCubePos() { boolean cubePosChanged() { // did the player move far enough to matter? - return blockToCube(playerEntity.posX) != this.getManagedCubePosX() - || blockToCube(playerEntity.posY) != this.getManagedCubePosY() - || blockToCube(playerEntity.posZ) != this.getManagedCubePosZ(); + return blockToCube(player.posX) != this.getManagedCubePosX() + || blockToCube(player.posY) != this.getManagedCubePosY() + || blockToCube(player.posZ) != this.getManagedCubePosZ(); } } - public Iterable getWatchedCubes() { - return () -> new AbstractIterator() { + enum Dirtiness { + None, + Partial, + Full + } + + private class WatchedColumn extends ChunkCoordIntPair implements XZAddressable { + public Chunk column; + public final BooleanArray2D dirtyColumns = new BooleanArray2D(16, 16); + public final ReferenceOpenHashSet watchingPlayers = new ReferenceOpenHashSet<>(4); + public long lastInhabitedTime; + + public Dirtiness dirty = Dirtiness.None; + + public WatchedColumn(int x, int z) { + super(x, z); - final Iterator iterator = CubicPlayerManager.this.tickableCubeTracker.iterator(); + setColumn(provider.getLoadedColumn(x, z)); + } + + public void setColumn(@Nullable Chunk column) { + if (column != null) { + this.column = column; + this.lastInhabitedTime = column.inhabitedTime; - boolean shouldSkip(@Nullable Cube cube) { - if (cube == null) return true; - if (cube.isEmpty()) return true; - if (!cube.isPopulated()) return true; - return false; + requestFullSync(); } + } - @Override - protected Cube computeNext() { - while (iterator.hasNext()) { - CubeWatcher watcher = iterator.next(); - Cube cube = watcher.getCube(); - if (shouldSkip(cube)) continue; - return cube; - } - return this.endOfData(); + public void addPlayer(WatchingPlayer player) { + if (watchingPlayers.contains(player)) return; + + watchingPlayers.add(player); + + if (column != null) { + PacketEncoderColumn.createPacket(column).sendToPlayer(player.player); } - }; + } + + public void removePlayer(WatchingPlayer player) { + if (!watchingPlayers.contains(player)) return; + + watchingPlayers.remove(player); + + if (column != null) { + PacketEncoderUnloadColumn.createPacket(chunkXPos, chunkZPos).sendToPlayer(player.player); + } + } + + public void markDirty(int blockX, int blockZ) { + if (this.dirty == Dirtiness.Full) return; + + blockX = Coords.blockToLocal(blockX); + blockZ = Coords.blockToLocal(blockZ); + + dirtyColumns.set(blockX, blockZ); + + if (this.dirty == Dirtiness.None) { + this.dirty = Dirtiness.Partial; + CubicPlayerManager.this.dirtyColumns.add(this); + } else if (dirtyColumns.cardinality() >= ForgeModContainer.clumpingThreshold) { + requestFullSync(); + } + } + + public void requestFullSync() { + this.dirty = Dirtiness.Full; + dirtyColumns.clear(); + CubicPlayerManager.this.dirtyColumns.add(this); + } + + public void clean() { + this.dirtyColumns.clear(); + this.dirty = Dirtiness.None; + } + + private void syncFull() { + var packet = PacketEncoderColumn.createPacket(column); + + for (WatchingPlayer player : this.watchingPlayers) { + packet.sendToPlayer(player.player); + } + } + + private void syncPartial() { + var packet = PacketEncoderHeightMapUpdate.createPacket(dirtyColumns, column); + + for (WatchingPlayer player : this.watchingPlayers) { + packet.sendToPlayer(player.player); + } + } + + @Override + public int getX() { + return chunkXPos; + } + + @Override + public int getZ() { + return chunkZPos; + } } - public static class TickableChunkContainer { + private class WatchedCube extends CubePos { + public EagerCubeLoadRequest request; + public Cube cube; + public final ShortArrayList dirtyBlocks = new ShortArrayList(8); + public final ArrayList watchingPlayers = new ArrayList<>(1); - private final ObjectArrayList cubes = ObjectArrayList.wrap(new ICube[64 * 1024]); - private XYZMap forcedCubes; - private final Set columns = Collections.newSetFromMap(new IdentityHashMap<>()); + private Dirtiness dirty = Dirtiness.None; - private void clear() { - this.cubes.clear(); - this.columns.clear(); + public WatchedCube(int cubeX, int cubeY, int cubeZ) { + super(cubeX, cubeY, cubeZ); + + setCube(provider.getLoadedCube(cubeX, cubeY, cubeZ)); } - private void addCube(ICube cube) { - cubes.add(cube); + public void setCube(@Nullable Cube cube) { + if (cube != null && cube.getInitLevel() == CubeInitLevel.Lit) { + this.cube = cube; + + requestFullSync(); + + if (this.request != null) { + if (!this.request.isCompleted()) this.request.cancel(); + this.request = null; + } + } } - public void addColumn(Chunk column) { - columns.add(column); + public void markDirty(int blockX, int blockY, int blockZ) { + if (this.dirty == Dirtiness.Full) return; + + blockX = Coords.blockToLocal(blockX); + blockY = Coords.blockToLocal(blockY); + blockZ = Coords.blockToLocal(blockZ); + + dirtyBlocks.add((short) AddressTools.getLocalAddress(blockX, blockY, blockZ)); + + if (this.dirty == Dirtiness.None) { + this.dirty = Dirtiness.Partial; + CubicPlayerManager.this.dirtyCubes.add(this); + } else if (dirtyBlocks.size() >= ForgeModContainer.clumpingThreshold) { + requestFullSync(); + } + + CubeStatusVisualizer.put(new CubePos(this), CubeStatus.Dirty); + } + + public void requestFullSync() { + this.dirty = Dirtiness.Full; + dirtyBlocks.clear(); + CubicPlayerManager.this.dirtyCubes.add(this); + } + + public void addPlayer(WatchingPlayer player) { + if (watchingPlayers.contains(player)) return; + + watchingPlayers.add(player); + + if (cube != null) { + player.queueCube(cube); + } else { + if (request == null || request.isCompleted()) { + request = provider.loadCubeEagerly(getX(), getY(), getZ(), Requirement.LIGHT); + } + } } - public Iterable forcedCubes() { - return forcedCubes; + public void removePlayer(WatchingPlayer player) { + if (!watchingPlayers.contains(player)) return; + + watchingPlayers.remove(player); + + if (cube != null) { + PacketEncoderUnloadCube.createPacket(cube.getCoords()).sendToPlayer(player.player); + } + + if (watchingPlayers.isEmpty() && this.request != null) this.request.cancel(); } - public ICube[] playerTickableCubes() { - return cubes.elements(); + public void clean() { + this.dirtyBlocks.clear(); + this.dirty = Dirtiness.None; } - public Iterable columns() { - return columns; + private void syncPartial() { + // send all the dirty blocks + short[] dirtyBlocks = this.dirtyBlocks.toShortArray(); + var cubePacket = PacketEncoderCubeBlockChange.createPacket(this.cube, dirtyBlocks); + + List tiles = new ArrayList<>(0); + + int blockX = this.cube.getX() << 4; + int blockY = this.cube.getY() << 4; + int blockZ = this.cube.getZ() << 4; + + for (int i = 0, localAddressesLength = dirtyBlocks.length; i < localAddressesLength; i++) { + short localAddress = dirtyBlocks[i]; + + int x = AddressTools.getLocalX(localAddress); + int y = AddressTools.getLocalY(localAddress); + int z = AddressTools.getLocalZ(localAddress); + + TileEntity te = getWorldServer().getTileEntity(blockX + x, blockY + y, blockZ + z); + + if (te == null) continue; + + Packet packet = te.getDescriptionPacket(); + + if (packet != null) tiles.add(packet); + } + + for (WatchingPlayer player : this.watchingPlayers) { + cubePacket.sendToPlayer(player.player); + + for (Packet tilePacket : tiles) { + player.player.playerNetServerHandler.sendPacket(tilePacket); + } + } } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java index 4aaf9bc8..94c0ad5f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java @@ -18,8 +18,9 @@ public class CCNBTUtils { public static NBTTagCompound loadTag(byte[] data) throws IOException { if (data[0] == (byte) 0x1f && data[1] == (byte) 0x8b) { - GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(data)); - data = IOUtils.toByteArray(gzip); + try (GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(data));) { + data = IOUtils.toByteArray(gzip); + } } return CompressedStreamTools.func_152456_a(new DataInputStream(new ByteArrayInputStream(data)), NBTSizeTracker.field_152451_a); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index fd49ae80..32f6645a 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.TimeUnit; @@ -33,8 +34,8 @@ public class CubeIO implements ICubeIO, IThreadedFileIO { private final ICubicStorage storage; private final IPreloadFailureDelegate preloadFailures; - private final ITaskExecutor columnLoadExecutor; - private final ITaskExecutor cubeLoadExecutor; + private final ITaskExecutor> columnLoadExecutor; + private final ITaskExecutor> cubeLoadExecutor; private final LinkedTransferQueue> columnQueue = new LinkedTransferQueue<>(); private final LinkedTransferQueue> cubeQueue = new LinkedTransferQueue<>(); @@ -43,7 +44,7 @@ public class CubeIO implements ICubeIO, IThreadedFileIO { private final Map pendingCubes = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") - private final Cache columnCache = ((CacheBuilder) (Object) CacheBuilder + private final Cache> columnCache = ((CacheBuilder>) (Object) CacheBuilder .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) .softValues() .initialCapacity(512) @@ -51,7 +52,7 @@ public class CubeIO implements ICubeIO, IThreadedFileIO { .build(); @SuppressWarnings("unchecked") - private final Cache cubeCache = ((CacheBuilder) (Object) CacheBuilder + private final Cache> cubeCache = ((CacheBuilder>) (Object) CacheBuilder .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) .softValues() .initialCapacity(1024) @@ -72,7 +73,7 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { for (var task : tasks) { NBTTagCompound tag = result.columns.get(task.getTask()); - task.finish(tag); + task.finish(Optional.ofNullable(tag)); } } catch (IOException e) { for (var task : tasks) { @@ -91,7 +92,7 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { for (var task : tasks) { NBTTagCompound tag = result.cubes.get(task.getTask()); - task.finish(tag); + task.finish(Optional.ofNullable(tag)); } } catch (IOException e) { for (var task : tasks) { @@ -137,14 +138,22 @@ public NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureExcept if (tag != null) return (NBTTagCompound) tag.copy(); - tag = columnCache.getIfPresent(pos); + Optional cached = columnCache.getIfPresent(pos); - if (tag != null) return (NBTTagCompound) tag.copy(); + if (cached != null) { + columnCache.invalidate(pos); + + if (cached.isPresent()) { + return (NBTTagCompound) cached.get().copy(); + } else { + return null; + } + } try { tag = storage.readColumn(pos); - if (tag == null) return null; + if (tag == null) columnCache.put(pos, Optional.empty()); return tag; } catch (IOException e) { @@ -159,14 +168,22 @@ public NBTTagCompound loadCube(CubePos pos) throws LoadFailureException { if (tag != null) return (NBTTagCompound) tag.copy(); - tag = cubeCache.getIfPresent(pos); + Optional cached = cubeCache.getIfPresent(pos); - if (tag != null) return (NBTTagCompound) tag.copy(); + if (cached != null) { + cubeCache.invalidate(pos); + + if (cached.isPresent()) { + return (NBTTagCompound) cached.get().copy(); + } else { + return null; + } + } try { tag = storage.readCube(pos); - if (tag == null) return null; + if (tag == null) cubeCache.put(pos, Optional.empty()); return tag; } catch (IOException e) { @@ -293,18 +310,14 @@ public void preloadColumn(ChunkCoordIntPair pos) { @Override public void preloadCube(CubePos pos, CubeInitLevel wanted) { - TaskPool.submit(cubeLoadExecutor, pos, tag -> { - CubeInitLevel actual = tag == null ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag); + if (preloadFailures != null) { + preloadFailures.onCubePreloadFailed(pos, CubeInitLevel.None, wanted); + } - if (tag == null || actual.ordinal() < wanted.ordinal()) { - if (preloadFailures != null) { - preloadFailures.onCubePreloadFailed(pos, actual, wanted); - } - } + TaskPool.submit(cubeLoadExecutor, pos, tag -> { + CubeInitLevel actual = !tag.isPresent() ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag.get()); - if (tag != null) { - cubeCache.put(pos, tag); - } + cubeCache.put(pos, tag); }); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index 4256f3c8..efd80fc7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -15,6 +15,7 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; +import com.cardinalstar.cubicchunks.api.MetaKey; import com.cardinalstar.cubicchunks.api.XYZAddressable; import com.cardinalstar.cubicchunks.api.XYZMap; import com.cardinalstar.cubicchunks.api.XZMap; @@ -24,8 +25,8 @@ import com.cardinalstar.cubicchunks.event.events.ColumnEvent; import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.server.CubicPlayerManager; import com.cardinalstar.cubicchunks.util.Array3D; -import com.cardinalstar.cubicchunks.util.BlockPosMap; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.XZAddressable; import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; @@ -33,9 +34,12 @@ import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.Setter; public class CubeLoaderServer implements ICubeLoader { + private static final MetaKey CUBE_INFO = new MetaKey<>() { }; + private final WorldServer world; private final ICubeIO cubeIO; private final IWorldGenerator generator; @@ -49,6 +53,8 @@ public class CubeLoaderServer implements ICubeLoader { private final List pendingColumnLoads = new ArrayList<>(); private Array3D cache; + @Setter + private long now; public CubeLoaderServer(WorldServer world, ICubicStorage storage, IWorldGenerator generator, CubeLoaderCallback callback) { @@ -140,13 +146,17 @@ public Cube getLoadedCube(int x, int y, int z) { } } - CubeInfo cube = cubes.get(x, y, z); + CubeInfo info = cubes.get(x, y, z); + + Cube cube = info == null ? null : info.cube; if (cache == null) { - lastCube = cube == null ? null : cube.cube; + lastCube = cube; + } else { + cache.set(x, y, z, cube); } - return cube == null ? null : cube.cube; + return cube; } @Override @@ -208,12 +218,16 @@ public Cube getCube(int x, int y, int z, Requirement effort) { cache.set(x, y, z, cubeInfo.cube); } + if (success) { + cubeInfo.lastAccess = now; + } + return success ? cubeInfo.cube : null; } @Override - public void onCubeGenerated(int x, int y, int z) { - CubeInfo cubeInfo = cubes.get(x, y, z); + public void onCubeGenerated(Cube cube) { + CubeInfo cubeInfo = cube.getMeta(CUBE_INFO); if (cubeInfo == null) return; @@ -223,16 +237,8 @@ public void onCubeGenerated(int x, int y, int z) { } @Override - public void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz, Requirement effort) { + public void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz) { cache = new Array3D<>(spanx, spany, spanz, x, y, z, new Cube[spanx * spany * spanz]); - - for (int x2 = 0; x2 < spanx; x2++) { - for (int y2 = 0; y2 < spany; y2++) { - for (int z2 = 0; z2 < spanz; z2++) { - cache.set(x + x2, y + y2, z + z2, getCube(x + x2, y + y2, z + z2, effort)); - } - } - } } @Override @@ -318,22 +324,32 @@ public void saveCube(Cube cube) { cubeIO.saveCube(cubeInfo.pos, cube); } + private static final int CUBE_GC_EXPIRY = 20 * 5; + @Override public void doGC() { var persistentChunks = ForgeChunkManager.getPersistentChunksFor(world); + CubicPlayerManager playerManager = (CubicPlayerManager) world.getPlayerManager(); List pendingCubeUnloads = new ArrayList<>(); + int startCubes = cubes.getSize(); + int startCols = columns.getSize(); + + final long expiry = now - CUBE_GC_EXPIRY; + for (CubeInfo cubeInfo : cubes) { Cube cube = cubeInfo.cube; - if (cube == null) continue; + if (cube == null || cubeInfo.lastAccess > expiry) continue; if (persistentChunks.containsKey( cube.getColumn() .getChunkCoordIntPair())) continue; + if (playerManager.isCubeWatched(cube.getX(), cube.getY(), cube.getZ())) continue; + if (cube.getTickets() .canUnload()) { pendingCubeUnloads.add(cubeInfo.pos); @@ -344,6 +360,8 @@ public void doGC() { unloadCube(pos.getX(), pos.getY(), pos.getZ()); } + int autoCols = columns.getSize(); + List pendingColumnUnloads = new ArrayList<>(); for (ColumnInfo columnInfo : columns) { @@ -357,8 +375,7 @@ public void doGC() { if (!columnInfo.containedCubes.isEmpty()) continue;; // PlayerChunkMap may contain reference to a column that for a while doesn't yet have any cubes generated - if (world.getPlayerManager() - .func_152621_a(column.xPosition, column.zPosition)) continue; + if (playerManager.func_152621_a(column.xPosition, column.zPosition)) continue; pendingColumnUnloads.add(columnInfo); } @@ -366,6 +383,11 @@ public void doGC() { for (ColumnInfo column : pendingColumnUnloads) { unloadColumn(column); } + + CubicChunks.LOGGER.info("Garbage collected {} columns ({} -> {}) and {} cubes ({} -> {}). Removed {} columns automatically because they were empty.", + pendingColumnUnloads.size(), startCols, columns.getSize(), + pendingCubeUnloads.size(), startCubes, cubes.getSize(), + startCols - autoCols); } @Override @@ -412,6 +434,7 @@ private void handleSideEffects(GenerationResult result) { info.source = ObjectSource.GeneratedSideEffect; info.cube = cube; + cube.setMeta(CUBE_INFO, info); info.onCubeLoaded(); callback.onCubeGenerated(cube, cube.getInitLevel()); @@ -449,7 +472,7 @@ public int getZ() { return pos.chunkZPos; } - public boolean initialize(Requirement effort) throws IOException { + public boolean initialize(Requirement effort) { if (column != null) return true; if (effort == Requirement.GET_CACHED) return false; @@ -546,6 +569,7 @@ private class CubeInfo implements XYZAddressable { private ObjectSource source = ObjectSource.None; public boolean generating = false; + public long lastAccess = 0; private CubeInitLevel lastKnownLevel = CubeInitLevel.None; @@ -619,6 +643,7 @@ private void loadCube() throws IOException { ensureColumn(); this.cube = IONbtReader.readCube(column.column, getX(), getY(), getZ(), tag); + cube.setMeta(CUBE_INFO, this); source = ObjectSource.Disk; @@ -667,12 +692,12 @@ private boolean generate(CubeInitLevel requestedInitLevel) { if (result == null) return false; this.cube = result.object; + cube.setMeta(CUBE_INFO, this); + source = ObjectSource.Generated; onCubeLoaded(); - cubeIO.saveCube(pos, cube); - handleSideEffects(result); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java index 73006dc4..13731b8e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeLoader.java @@ -28,19 +28,20 @@ default boolean columnExists(int x, int z) { Cube getCube(int x, int y, int z, Requirement effort); /// Notifies this loader that a cube was generated further, either by a populator or something else. - void onCubeGenerated(int x, int y, int z); + void onCubeGenerated(Cube cube); - void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz, Requirement effort); + void cacheCubes(int x, int y, int z, int spanx, int spany, int spanz); + + void setNow(long now); default void cacheCubes(Box box, Requirement effort) { cacheCubes( box.getX1(), box.getY1(), box.getZ1(), - box.getX2() - box.getX1(), - box.getY2() - box.getY1(), - box.getZ2() - box.getZ1(), - effort); + box.getX2() - box.getX1() + 1, + box.getY2() - box.getY1() + 1, + box.getZ2() - box.getZ1() + 1); } void uncacheCubes(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java index a37dd74b..825951e2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java @@ -276,7 +276,7 @@ private Map compressNBTForBatchWrite(Map keyMappingFunction.apply(entry.getKey()), entry -> { try { - return CCNBTUtils.saveTag(entry.getValue(), false); + return CCNBTUtils.saveTag(entry.getValue(), true); } catch (IOException e) { // wrap exception so that we can throw it from inside the lambda throw new UncheckedIOException(e); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java new file mode 100644 index 00000000..c6d001a3 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java @@ -0,0 +1,69 @@ +package com.cardinalstar.cubicchunks.util; + +import static com.gtnewhorizon.gtnhlib.util.CoordinatePacker.pack; + +import java.util.Iterator; + +import org.joml.Vector3i; +import org.joml.Vector3ic; + +import com.cardinalstar.cubicchunks.api.XYZAddressable; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + +@SuppressWarnings("unused") +public class BlockPosSet extends LongOpenHashSet { + + public boolean contains(int blockX, int blockY, int blockZ) { + return super.contains(pack(blockX, blockY, blockZ)); + } + + public boolean contains(XYZAddressable xyz) { + return contains(xyz.getX(), xyz.getY(), xyz.getZ()); + } + + public boolean remove(int blockX, int blockY, int blockZ) { + return super.remove(pack(blockX, blockY, blockZ)); + } + + public boolean remove(XYZAddressable xyz) { + return remove(xyz.getX(), xyz.getY(), xyz.getZ()); + } + + public boolean add(int blockX, int blockY, int blockZ) { + return super.add(pack(blockX, blockY, blockZ)); + } + + public boolean add(XYZAddressable xyz) { + return add(xyz.getX(), xyz.getY(), xyz.getZ()); + } + + public Iterator fastIterator() { + LongIterator iter = super.iterator(); + + Vector3i v = new Vector3i(); + + return new Iterator<>() { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Vector3ic next() { + long l = iter.nextLong(); + + v.x = Coords.x(l); + v.y = Coords.y(l); + v.z = Coords.z(l); + + return v; + } + }; + } + + public Iterable fastEntryIterable() { + return this::fastIterator; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray2D.java b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray2D.java new file mode 100644 index 00000000..df0ea1bb --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray2D.java @@ -0,0 +1,88 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.BitSet; +import java.util.Iterator; + +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import com.google.common.collect.AbstractIterator; + +public class BooleanArray2D extends BitSet implements Iterable { + + private final int spanx, spany; + + public BooleanArray2D(int spanx, int spany) { + this.spanx = spanx; + this.spany = spany; + } + + public BooleanArray2D(int spanx, int spany, boolean[] data) { + this.spanx = spanx; + this.spany = spany; + + for (int i = 0; i < data.length; i++) { + if (data[i]) { + set(i); + } + } + } + + public BooleanArray2D(int spanx, int spany, byte[] data) { + this.spanx = spanx; + this.spany = spany; + + this.or(BitSet.valueOf(data)); + } + + public void set(int x, int y) { + set(index(x, y)); + } + + public void clear(int x, int y) { + clear(index(x, y)); + } + + public boolean get2d(int x, int y) { + return get(index(x, y)); + } + + @Override + public @NotNull Iterator iterator() { + return new AbstractIterator<>() { + + private boolean init = false; + private int index; + private final Vector2i v = new Vector2i(); + + @Override + protected Vector2ic computeNext() { + if (!init) { + init = true; + index = BooleanArray2D.this.nextSetBit(0); + } else { + index = BooleanArray2D.this.nextSetBit(index + 1); + } + + if (index == -1) { + this.endOfData(); + return null; + } + + v.set(index % spanx, (index / spanx) % spany); + + return v; + } + }; + } + + private int index(int x, int y) { + return x + (y * spanx); + } + + @Override + public BooleanArray2D clone() { + return (BooleanArray2D) super.clone(); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java index d82ec8b4..5a6c8970 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java @@ -3,6 +3,7 @@ import java.util.BitSet; import java.util.Iterator; +import org.jetbrains.annotations.NotNull; import org.joml.Vector3i; import org.joml.Vector3ic; @@ -44,7 +45,8 @@ public boolean get(int x, int y, int z) { return get(index(x, y, z)); } - public Iterator iterator() { + @Override + public @NotNull Iterator iterator() { return new AbstractIterator<>() { private boolean init = false; @@ -75,4 +77,9 @@ protected Vector3ic computeNext() { private int index(int x, int y, int z) { return x + (y * spanx) + (z * spanslice); } + + @Override + public BooleanArray2D clone() { + return (BooleanArray2D) super.clone(); + } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubePos.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubePos.java index 581ba05f..a7771b39 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/CubePos.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubePos.java @@ -284,8 +284,8 @@ public boolean containsBlock(int x, int y, int z) { return this.cubeX == blockToCube(x) && this.cubeY == blockToCube(y) && this.cubeZ == blockToCube(z); } - public static long cubeXYZToLong(int x, int y, int z) { - return (long) x & 4294967295L | ((long) y & 4294967295L) | ((long) x & 4294967295L) << 32; + public static CubePos unpack(long coord) { + return new CubePos(Coords.x(coord), Coords.y(coord), Coords.z(coord)); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java new file mode 100644 index 00000000..eed1442d --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -0,0 +1,65 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; +import cpw.mods.fml.common.gameevent.TickEvent.Phase; +import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent; + +@EventBusSubscriber +public class CubeStatusVisualizer { + + public enum CubeStatus { + None, + Generated, + Populated, + Lit, + Dirty, + Synced + } + + private static final ConcurrentHashMap cubeStatus = new ConcurrentHashMap<>(); + private static final AtomicBoolean dirty = new AtomicBoolean(); + + public static void sync(ServerTickEvent event) { + if (event.phase != Phase.END) return; + if (!dirty.compareAndSet(true, false)) return; + +// List boxes = new ArrayList<>(); +// +// cubeStatus.forEach((pos, status) -> { +// boxes.add(new VisualizedBox( +// switch (status) { +// case None -> new Color(100, 50, 100); +// case Generated -> new Color(50, 200, 50); +// case Populated -> new Color(50, 50, 200); +// case Lit -> new Color(200, 200, 50); +// case Synced -> new Color(200, 50, 50); +// }, +// new AABBf( +// pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), +// pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ()) +// )); +// }); +// +// for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { +// BoxVisualizer.sendBoxes(player, Duration.ofMinutes(5), boxes, true); +// } + } + + public static void put(CubePos pos, CubeStatus status) { + cubeStatus.put(pos, status); + dirty.set(true); + } + + public static void cmpexc(CubePos pos, CubeStatus expected, CubeStatus desired) { + cubeStatus.compute(pos, (key, existing) -> existing == expected ? desired : existing); + dirty.set(true); + } + + public static void remove(CubePos pos) { + cubeStatus.remove(pos); + dirty.set(true); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java b/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java index fc21ba1c..90029b10 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java +++ b/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java @@ -32,6 +32,10 @@ @ParametersAreNonnullByDefault public class CuboidalCubeSelector extends CubeSelector { + public static final CuboidalCubeSelector INSTANCE = new CuboidalCubeSelector(); + + private CuboidalCubeSelector() { } + @Override public void forAllVisibleFrom(CubePos cubePos, int horizontalViewDistance, int verticalViewDistance, Consumer consumer) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java b/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java index 6e0538f8..f94fd26b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java @@ -23,10 +23,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Random; -import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -44,14 +43,19 @@ import net.minecraft.world.biome.BiomeGenBase; import net.minecraftforge.event.ForgeEventFactory; -import com.cardinalstar.cubicchunks.server.CubeWatcher; +import org.joml.Vector3ic; + +import com.cardinalstar.cubicchunks.api.util.Box; import com.cardinalstar.cubicchunks.server.CubicPlayerManager; +import com.cardinalstar.cubicchunks.util.BlockPosSet; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.MathUtil; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; - import cpw.mods.fml.common.eventhandler.Event; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import it.unimi.dsi.fastutil.longs.LongLists; @ParametersAreNonnullByDefault public class CubeSpawnerAnimals implements ISpawnerAnimals { @@ -61,7 +65,7 @@ public class CubeSpawnerAnimals implements ISpawnerAnimals { private static final int SPAWN_RADIUS = 8; @Nonnull - private Set cubesForSpawn = new HashSet<>(); + private final BlockPosSet cubesForSpawn = new BlockPosSet(); @Override public int findChunksForSpawning(WorldServer world, boolean hostileEnable, boolean peacefulEnable, @@ -71,70 +75,56 @@ public int findChunksForSpawning(WorldServer world, boolean hostileEnable, boole } this.cubesForSpawn.clear(); - int chunkCount = addEligibleCubes(world, this.cubesForSpawn); + int cubeCount = addEligibleCubes(world, this.cubesForSpawn); int totalSpawnCount = 0; for (EnumCreatureType mobType : EnumCreatureType.values()) { if (!shouldSpawnType(mobType, hostileEnable, peacefulEnable, spawnOnSetTickRate)) { continue; } + int worldEntityCount = world.countEntities(mobType, true); - int maxEntityCount = mobType.getMaxNumberOfCreature() * chunkCount / MOB_COUNT_DIV; + int maxEntityCount = mobType.getMaxNumberOfCreature() * cubeCount / MOB_COUNT_DIV; if (worldEntityCount > maxEntityCount) { continue; } - List shuffled = getShuffledCopy(this.cubesForSpawn) - .subList(0, Math.min(this.cubesForSpawn.size(), 2 * (2 * SPAWN_RADIUS + 1))); - totalSpawnCount += spawnCreatureTypeInAllChunks(mobType, world, shuffled); + + LongList shuffled = new LongArrayList(cubesForSpawn); + LongLists.shuffle(shuffled, world.rand); + shuffled = shuffled.subList(0, Math.min(this.cubesForSpawn.size(), 2 * (2 * SPAWN_RADIUS + 1))); + + List cubes = shuffled.longStream().mapToObj(CubePos::unpack).collect(Collectors.toList()); + + totalSpawnCount += spawnCreatureTypeInAllChunks(mobType, world, cubes); } + return totalSpawnCount; } - private int addEligibleCubes(WorldServer world, Set possibleCubes) { - int chunkCount = 0; - Random r = world.rand; - Set allCubes = new HashSet<>(); - for (EntityPlayer player : world.playerEntities) { - CubePos center = CubePos.fromEntity(player); + private int addEligibleCubes(WorldServer world, BlockPosSet possibleCubes) { + int cubeCount = 0; - for (int cubeXRel = -SPAWN_RADIUS; cubeXRel <= SPAWN_RADIUS; ++cubeXRel) { - for (int cubeYRel = -SPAWN_RADIUS; cubeYRel <= SPAWN_RADIUS; ++cubeYRel) { - for (int cubeZRel = -SPAWN_RADIUS; cubeZRel <= SPAWN_RADIUS; ++cubeZRel) { - CubePos cubePos = center.add(cubeXRel, cubeYRel, cubeZRel); + BlockPosSet checkedCubes = new BlockPosSet(); - if (allCubes.contains(cubePos)) { - continue; - } - assert !possibleCubes.contains(cubePos); - ++chunkCount; + for (EntityPlayer player : world.playerEntities) { + CubePos center = CubePos.fromEntity(player); - boolean isEdge = cubeXRel == -SPAWN_RADIUS || cubeXRel == SPAWN_RADIUS - || cubeYRel == -SPAWN_RADIUS - || cubeYRel == SPAWN_RADIUS - || cubeZRel == -SPAWN_RADIUS - || cubeZRel == SPAWN_RADIUS; + for (Vector3ic v : new Box(center.getX(), center.getY(), center.getZ(), SPAWN_RADIUS - 1)) { + if (!checkedCubes.add(v.x(), v.y(), v.z())) continue; - if (isEdge) { - continue; - } - CubeWatcher cubeInfo = ((CubicPlayerManager) world.getPlayerManager()).getCubeWatcher(cubePos); + assert !possibleCubes.contains(v.x(), v.y(), v.z()); + cubeCount++; - if (cubeInfo != null && cubeInfo.isSentToPlayers()) { - allCubes.add(cubePos); + boolean valid = ((CubicPlayerManager) world.getPlayerManager()).isCubeWatchedAndPresent(v.x(), v.y(), v.z()); - // TODO Make this spawn culling more intelligent. Also - // maybe make this biased for the surface? - if (!cubeInfo.getCube() - .isEmpty()) { - possibleCubes.add(cubePos); - } - } - } + if (valid) { + possibleCubes.add(v.x(), v.y(), v.z()); } } } - return chunkCount; + + return cubeCount; } private int spawnCreatureTypeInAllChunks(EnumCreatureType mobType, WorldServer world, List cubeList) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java b/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java index 492f8e95..189a5789 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java @@ -24,12 +24,10 @@ import java.lang.invoke.MethodHandle; import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.annotation.ParametersAreNonnullByDefault; -import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.world.ChunkCoordIntPair; @@ -41,11 +39,7 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.CubicChunksConfig; -import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.mixin.early.common.forge.IForgeChunkManager; -import com.cardinalstar.cubicchunks.server.ColumnWatcher; -import com.cardinalstar.cubicchunks.server.CubicPlayerManager; -import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.ITicket; import com.cardinalstar.cubicchunks.util.ReflectionUtil; @@ -53,7 +47,6 @@ import com.cardinalstar.cubicchunks.world.core.ICubicTicketInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; - import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; @@ -235,47 +228,47 @@ public static void onForgeChunkManagerForceChunk(ForgeChunkManager.ForceChunkEve if (!(worldInstance instanceof WorldServer)) { return; } - addForcedCubesHeuristic(event, ticket, (WorldServer) worldInstance); + // TODO: properly implement chunk loading +// addForcedCubesHeuristic(event, ticket, (WorldServer) worldInstance); } - private static void addForcedCubesHeuristic(ForgeChunkManager.ForceChunkEvent event, - ForgeChunkManager.Ticket ticket, WorldServer worldInstance) { - IntSet yCoords = ((ICubicTicketInternal) ticket).getAllForcedChunkCubes() - .get(event.location); - if (yCoords != null && !yCoords.isEmpty()) { - yCoords.forEach( - cubeY -> ((ICubicWorldInternal) ticket.world) - .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos) - .getTickets() - .add((ITicket) ticket)); - return; - } - WorldServer world = worldInstance; - CubicPlayerManager cubeMap = (CubicPlayerManager) world.getPlayerManager(); - ColumnWatcher columnWatcher = cubeMap - .getColumnWatcher(new ChunkCoordIntPair(event.location.chunkXPos, event.location.chunkZPos)); - - if (columnWatcher == null) { - ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, new IntArraySet()); - return; // TODO: some different heuristic? - } - List players = columnWatcher.getWatchingPlayers(); - int verticalViewDistance = CubicChunksConfig.verticalCubeLoadDistance; - if (yCoords == null) { - yCoords = new IntArraySet(players.size() * verticalViewDistance * 3); - } - for (EntityPlayerMP player : players) { - for (int dy = -verticalViewDistance; dy <= verticalViewDistance; dy++) { - int cubeY = Coords.getCubeYForEntity(player) + dy; - Cube cube = (Cube) ((ICubicWorld) world) - .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos); - cube.getTickets() - .add((ITicket) ticket); - yCoords.add(cubeY); - } - } - ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, yCoords); - } +// private static void addForcedCubesHeuristic(ForgeChunkManager.ForceChunkEvent event, +// ForgeChunkManager.Ticket ticket, WorldServer worldInstance) { +// IntSet yCoords = ((ICubicTicketInternal) ticket).getAllForcedChunkCubes() +// .get(event.location); +// if (yCoords != null && !yCoords.isEmpty()) { +// yCoords.forEach( +// cubeY -> ((ICubicWorldInternal) ticket.world) +// .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos) +// .getTickets() +// .add((ITicket) ticket)); +// return; +// } +// WorldServer world = worldInstance; +// CubicPlayerManager cubeMap = (CubicPlayerManager) world.getPlayerManager(); +// ColumnWatcher columnWatcher = cubeMap.get; +// +// if (columnWatcher == null) { +// ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, new IntArraySet()); +// return; // TODO: some different heuristic? +// } +// List players = columnWatcher.getWatchingPlayers(); +// int verticalViewDistance = CubicChunksConfig.verticalCubeLoadDistance; +// if (yCoords == null) { +// yCoords = new IntArraySet(players.size() * verticalViewDistance * 3); +// } +// for (EntityPlayerMP player : players) { +// for (int dy = -verticalViewDistance; dy <= verticalViewDistance; dy++) { +// int cubeY = Coords.getCubeYForEntity(player) + dy; +// Cube cube = (Cube) ((ICubicWorld) world) +// .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos); +// cube.getTickets() +// .add((ITicket) ticket); +// yCoords.add(cubeY); +// } +// } +// ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, yCoords); +// } @SubscribeEvent public static void onForgeChunkManagerUnforceChunk(ForgeChunkManager.UnforceChunkEvent event) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java index ddc55945..ed232b13 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -63,9 +63,9 @@ import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.IHeightMap; +import com.cardinalstar.cubicchunks.api.MetaKey; import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.server.CubeWatcher; import com.cardinalstar.cubicchunks.server.SpawnCubes; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.CompatHandler; @@ -77,6 +77,7 @@ import com.cardinalstar.cubicchunks.world.core.ICubicTicketInternal; import com.cardinalstar.cubicchunks.world.cube.blockview.IBlockView; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; /** * A cube is our extension of minecraft's chunk system to three dimensions. Each cube encloses a cubic area in the world @@ -865,19 +866,29 @@ public EnumSet getForceLoadStatus() { if (this.tickets.anyMatch(t -> t instanceof SpawnCubes)) { forcedLoadReasons.add(ForcedLoadReason.SPAWN_AREA); } - if (this.tickets.anyMatch(t -> t instanceof CubeWatcher)) { - forcedLoadReasons.add(ForcedLoadReason.PLAYER); - } if (this.tickets.anyMatch(t -> t instanceof ICubicTicketInternal)) { forcedLoadReasons.add(ForcedLoadReason.MOD_TICKET); } if (this.tickets.anyMatch( - t -> !(t instanceof SpawnCubes) && !(t instanceof CubeWatcher) && !(t instanceof ICubicTicketInternal))) { + t -> !(t instanceof SpawnCubes) && !(t instanceof ICubicTicketInternal))) { forcedLoadReasons.add(ForcedLoadReason.OTHER); } return forcedLoadReasons; } + private final Object2ObjectArrayMap, Object> meta = new Object2ObjectArrayMap<>(); + + @Override + public T getMeta(MetaKey key) { + //noinspection unchecked + return (T) meta.get(key); + } + + @Override + public void setMeta(MetaKey key, T value) { + meta.put(key, value); + } + public interface ICubeLightTrackingInfo { boolean needsSaving(ICube cube); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java index a2ed9b1e..f2e8f578 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -1,7 +1,7 @@ package com.cardinalstar.cubicchunks.world.worldgen.vanilla; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import net.minecraft.world.gen.NoiseGeneratorOctaves; @@ -9,10 +9,10 @@ import com.cardinalstar.cubicchunks.async.TaskPool; import com.cardinalstar.cubicchunks.async.TaskPool.ITaskExecutor; import com.cardinalstar.cubicchunks.async.TaskPool.ITaskFuture; +import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.ObjectPooler; import com.github.bsideup.jabel.Desugar; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements PrecalculableNoise { @@ -21,18 +21,21 @@ public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements Pr private int xspan, yspan, zspan; private double xscale, yscale, zscale; private int misses, misses2, hits; + private AtomicInteger pres = new AtomicInteger(); + private long elapsed; private long lastMessage = System.nanoTime(); private final ObjectPooler dataPool = new ObjectPooler<>(NoiseData::new, null, 1024); private final Object paramLock = new Object(); - private final Object cacheLock = new Object(); - private final Cache cache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(8192) - .removalListener(notification -> releaseData((NoiseData) notification.getValue())) - .build(); +// private final Cache cache = CacheBuilder.newBuilder() +// .expireAfterWrite(5, TimeUnit.MINUTES) +// .maximumSize(8192) +// .removalListener(notification -> releaseData((NoiseData) notification.getValue())) +// .build(); + + private final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap<>(); private final ITaskExecutor noiseTaskExecutor; @@ -49,7 +52,13 @@ public void execute(List> tasks) { NoiseData data = task.run(); - cache.put(task.key, data); + synchronized (cache) { + cache.putAndMoveToFirst(Coords.key(task.key.x, task.key.y, task.key.z), data); + + while (cache.size() > 8192) cache.removeLast(); + } + + pres.addAndGet(1); future.finish(data); } @@ -73,9 +82,10 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int if ((now - lastMessage) > 5e9) { lastMessage = now; - CubicChunks.LOGGER.info("Hits: {} Misses: {}", hits, misses2); + CubicChunks.LOGGER.info("Hits: {} Misses: {} Pres: {} Per call: {}ms", hits, misses2, pres.getAndSet(0), (elapsed / (double)misses2 / 1e6)); hits = 0; misses2 = 0; + elapsed = 0; } synchronized (paramLock) { @@ -119,8 +129,8 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int NoiseData cached; - synchronized (cacheLock) { - cached = cache.getIfPresent(new TaskKey(blockX, blockY, blockZ)); + synchronized (cache) { + cached = cache.remove(Coords.key(blockX, blockY, blockZ)); } if (cached != null && cached.matches()) { @@ -131,7 +141,13 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int } misses2++; - return base.generateNoiseOctaves(data, blockX, blockY, blockZ, sx, sy, sz, scaleX, scaleY, scaleZ); + + long pre = System.nanoTime(); + double[] d2 = base.generateNoiseOctaves(data, blockX, blockY, blockZ, sx, sy, sz, scaleX, scaleY, scaleZ); + long post = System.nanoTime(); + elapsed += post - pre; + + return d2; } private NoiseData getData() { diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index ff7b8fc3..69dff2cb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -213,8 +213,8 @@ private FillerInfo analyzeTopFiller(IBlockView blockView) { @Override public void onColumnPreloadFailed(ChunkCoordIntPair pos) { if (vanilla instanceof Precalculable precalc) { - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { precalc.precalculate(pos.chunkXPos + dx, 0, pos.chunkZPos + dz); } } @@ -446,10 +446,7 @@ public void populate(Cube cube) { Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); center.markPopulated(Cube.POP_ALL); - } - - for (Vector3ic v : getCubesToGenerate(cx, cy, cz)) { - loader.onCubeGenerated(v.x(), v.y(), v.z()); + loader.onCubeGenerated(center); } if (!cube.isFullyPopulated()) { @@ -562,8 +559,8 @@ public void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel if (generate) { if (vanilla instanceof Precalculable precalc) { - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { precalc.precalculate(pos.getX() + dx, 0, pos.getZ() + dz); } } From 40122625a89e901be2a6f6d1081c5d99ae12518d Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Thu, 27 Nov 2025 15:40:17 -0500 Subject: [PATCH 10/22] MORE WORLDGEN FIXES!!! + chunk api/endless ids compat + 3d biomes + other misc fixes --- .../cardinalstar/cubicchunks/CubicChunks.java | 4 + .../cubicchunks/CubicChunksConfig.java | 4 + .../cardinalstar/cubicchunks/api/CCAPI.java | 78 +++++ .../cardinalstar/cubicchunks/api/ICube.java | 31 +- .../cardinalstar/cubicchunks/api/XYZMap.java | 12 + .../api/worldgen/IWorldGenerator.java | 2 +- .../cubicchunks/async/TaskPool.java | 10 + .../lighting/FirstLightProcessor.java | 20 +- .../lighting/phosphor/LightingHooks.java | 2 +- .../cubicchunks/mixin/Mixins.java | 6 + .../mixin/api/ICubicWorldInternal.java | 4 +- .../mixin/early/client/MixinWorldClient.java | 6 - .../common/AccessorS23PacketBlockChange.java | 28 ++ .../mixin/early/common/MixinChunk_Cubes.java | 11 +- .../mixin/early/common/MixinWorld.java | 27 ++ .../mixin/early/common/MixinWorldServer.java | 10 +- .../worldgen/MixinChunkProviderGenerate.java | 19 -- .../cubicchunks/network/CCPacketBuffer.java | 39 +++ .../cubicchunks/network/CCPacketEncoder.java | 12 +- .../cubicchunks/network/CCPacketEntry.java | 5 +- .../network/PacketEncoderCubeBlockChange.java | 106 +++--- .../network/PacketEncoderCubes.java | 4 +- .../network/PacketEncoderWorldHeight.java | 44 +++ .../cubicchunks/network/WorldEncoder.java | 243 +++++++------- .../server/CubeProviderServer.java | 36 +- .../server/CubicPlayerManager.java | 57 ++-- .../cubicchunks/server/chunkio/CubeIO.java | 316 +++++++++++------- .../server/chunkio/CubeLoaderServer.java | 118 ++++--- .../server/chunkio/IONbtReader.java | 120 ++++--- .../server/chunkio/IONbtWriter.java | 94 +++--- .../cubicchunks/util/AddressTools.java | 4 - .../util/CubeStatusVisualizer.java | 81 +++-- .../cubicchunks/util/DirectionUtils.java | 2 +- .../cardinalstar/cubicchunks/util/Mods.java | 6 +- .../cubicchunks/util/biome3d/BiomeArray.java | 22 ++ .../util/biome3d/DynamicBiomeArray.java | 145 ++++++++ .../biome3d/InvalidBiomeDataException.java | 8 + .../util/biome3d/PaletteFullError.java | 8 + .../util/biome3d/PalettizedBiomeArray.java | 131 ++++++++ .../util/biome3d/ReferenceBiomeArray.java | 55 +++ .../world/api/ICubeProviderServer.java | 2 +- .../world/core/ServerHeightMap.java | 69 ++-- .../cubicchunks/world/cube/Cube.java | 66 ++-- .../NoodleCaveGenerator.java | 0 .../SpaghettiCaveGenerator.java | 0 .../worldgen/VanillaWorldGenerator.java | 124 ++++--- .../assets/cubicchunks/lang/en_US.lang | 1 + 47 files changed, 1476 insertions(+), 716 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/AccessorS23PacketBlockChange.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderWorldHeight.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/ReferenceBiomeArray.java rename src/main/java/com/cardinalstar/cubicchunks/world/worldgen/{modern => caves}/NoodleCaveGenerator.java (100%) rename src/main/java/com/cardinalstar/cubicchunks/world/worldgen/{modern => caves}/SpaghettiCaveGenerator.java (100%) diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index 72d625c3..afa4a314 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -179,6 +179,10 @@ public void init(FMLInitializationEvent event) { @Mod.EventHandler public void postInit(FMLPostInitializationEvent event) { CompatHandler.init(); + + if (Mods.ChunkAPI.isModLoaded()) { + DataRegistry.disableDataManager("chunkapi", "lighting"); + } } @Mod.EventHandler diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java index edb2c7ba..b7c6561d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunksConfig.java @@ -98,6 +98,10 @@ public class CubicChunksConfig { + " client. Does not affect rendering, only what chunks are sent to client.") public static int verticalCubeLoadDistance = 8; + @Config.LangKey("cubicchunks.config.enable_chunk_debugging") + @Config.Comment("Displays coloured boxes over cubes at Y=8 for debugging purposes.") + public static boolean enableChunkStatusDebugging = false; + @Config.LangKey("cubicchunks.config.dimension_blacklist") @Config.Comment("The specified dimension ID ranges won't be created as cubic chunks world for new worlds, and worlds created before this option" + " has been added, unless forceDimensionExcludes is set to true. IDs can be specified either as range in format min:max, or as single " diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java new file mode 100644 index 00000000..15db1186 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java @@ -0,0 +1,78 @@ +package com.cardinalstar.cubicchunks.api; + +import java.util.Collection; +import java.util.Iterator; + +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import org.jetbrains.annotations.Nullable; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; +import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer.Requirement; +import com.cardinalstar.cubicchunks.world.column.EmptyEBS; +import com.google.common.collect.AbstractIterator; + +@SuppressWarnings("unused") +public final class CCAPI { + + private CCAPI() { } + + public static ExtendedBlockStorage getBlockStorage(Chunk chunk, int yLevel) { + ICube cube = ((IColumn) chunk).getCube(yLevel); + + if (cube == null) return new EmptyEBS(yLevel); + + return cube.getStorage(); + } + + /// Gets a loaded cube. Does not load or generate the cube. + @Nullable + public static ICube getLoadedCube(World world, int cubeX, int cubeY, int cubeZ) { + return ((ICubicWorld) world).getCubeCache().getLoadedCube(cubeX, cubeY, cubeZ); + } + + /// Gets a cube. Does terrain generation if the cube is not in memory and could not be loaded from disk. + public static ICube getCube(World world, int cubeX, int cubeY, int cubeZ) { + return ((ICubicWorld) world).getCubeFromCubeCoords(cubeX, cubeY, cubeZ); + } + + /// Gets a cube. The returned cube's status will match the given [Requirement]. + public static ICube getCube(World world, int cubeX, int cubeY, int cubeZ, Requirement effort) { + return ((ICubeProviderServer) ((ICubicWorld) world).getCubeCache()).getCube(cubeX, cubeY, cubeZ, effort); + } + + /// Gets all loaded cubes in a column. + public static Collection getLoadedCubes(Chunk chunk) { + //noinspection unchecked + return (Collection) ((IColumn) chunk).getLoadedCubes(); + } + + /// Gets all loaded block storages in a column. + public static Iterable getLoadedBlockStorages(Chunk chunk) { + return () -> new AbstractIterator<>() { + + private final Iterator iter = ((IColumn) chunk).getLoadedCubes().iterator(); + + @Override + protected ExtendedBlockStorage computeNext() { + while (iter.hasNext()) { + ICube cube = iter.next(); + + if (cube == null) continue; + + ExtendedBlockStorage ebs = cube.getStorage(); + + if (ebs == null) continue; + + return ebs; + } + + endOfData(); + return null; + } + }; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java index 224814e8..585b37cb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java @@ -38,6 +38,8 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import org.jetbrains.annotations.NotNull; + import com.cardinalstar.cubicchunks.api.worldgen.IWorldGenerator; import com.cardinalstar.cubicchunks.server.chunkio.CubeInitLevel; import com.cardinalstar.cubicchunks.util.CubePos; @@ -264,35 +266,20 @@ default boolean isInitializedToLevel(CubeInitLevel initLevel) { boolean hasLightUpdates(); + @NotNull BiomeGenBase getBiome(int x, int y, int z); /** * Set biome at a cube-local 4x4x4 block segment. * - * @param localBiomeX cube-local X coordinate. One unit is 4 blocks - * @param localBiomeY cube-local Y coordinate. One unit is 4 blocks - * @param localBiomeZ cube-local Z coordinate. One unit is 4 blocks - * @param biome biome at the given cube coordinates + * @param x cube-local block X coordinate + * @param y cube-local block Y coordinate + * @param z cube-local block Z coordinate + * @param biome The biome at the given cube coordinates, or null to defer to the column */ - void setBiome(int localBiomeX, int localBiomeY, int localBiomeZ, BiomeGenBase biome); - - /** - * Set biome at a cube-local 2x2 block column. - * - * @param localBiomeX cube-local X coordinate. One unit is 2 blocks - * @param localBiomeZ cube-local Z coordinate. One unit is 2 blocks - * @param biome biome at the given cube coordinates - * @deprecated Due to changes in Minecraft 1.15.x, biome storage will be changed to 1 biome per 4x4x4 blocks. Use - * {@link #setBiome(int, int, int, BiomeGenBase)} - */ - @Deprecated - default void setBiome(int localBiomeX, int localBiomeZ, BiomeGenBase biome) { - for (int biomeY = 0; biomeY < 4; biomeY++) { - setBiome(localBiomeX >> 1, biomeY, localBiomeZ >> 1, biome); - } - } + void setBiome(int x, int y, int z, BiomeGenBase biome); - public BlockPos localAddressToBlockPos(int localAddress); + BlockPos localAddressToBlockPos(int localAddress); /** * Returns a set of reasons this cube is forced to remain loaded if it's forced to remain loaded, diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java index b4b4d9f4..45974897 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java @@ -43,10 +43,18 @@ public class XYZMap implements Iterable { Long2ObjectOpenHashMap items = new Long2ObjectOpenHashMap<>(); public T remove(int x, int y, int z) { + if (x < -2097152 || x > 2097151) return null; + if (y < -2097152 || y > 2097151) return null; + if (z < -2097152 || z > 2097151) return null; + return items.remove(Coords.key(x, y, z)); } public final T get(int x, int y, int z) { + if (x < -2097152 || x > 2097151) return null; + if (y < -2097152 || y > 2097151) return null; + if (z < -2097152 || z > 2097151) return null; + return items.get(Coords.key(x, y, z)); } @@ -55,6 +63,10 @@ public final T get(XYZAddressable xyz) { } public final T put(T item) { + if (item.getX() < -2097152 || item.getX() > 2097151) return null; + if (item.getY() < -2097152 || item.getY() > 2097151) return null; + if (item.getZ() < -2097152 || item.getZ() > 2097151) return null; + return items.put(Coords.key(item.getX(), item.getY(), item.getZ()), item); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java index 1603156a..3202ae4c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/IWorldGenerator.java @@ -46,7 +46,7 @@ public interface IWorldGenerator { * * @return A CubePrimer with the generated blocks */ - GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ); + GenerationResult provideCube(@Nullable Chunk chunk, int cubeX, int cubeY, int cubeZ); /** * Generate column-global information such as biome data diff --git a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java index 0cd9ec42..3c2b9d5c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -123,6 +123,16 @@ public static Future submit(ITaskExecutor { void execute(List> tasks); diff --git a/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java b/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java index e6605aa2..aa715470 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java +++ b/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java @@ -32,10 +32,13 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.joml.Vector3ic; import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.ICube; +import com.cardinalstar.cubicchunks.api.util.Box; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.server.chunkio.ICubeLoader; import com.cardinalstar.cubicchunks.util.MathUtil; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; @@ -72,13 +75,20 @@ public void diffuseSkylight(ICube cube) { BlockPos maxPos = cube.getCoords() .getMaxBlockPos(); - Iterable allBlocks = BlockPos.getAllInBox(minPos.x, minPos.y, minPos.z, maxPos.x, maxPos.y, maxPos.z); - for (BlockPos pos : allBlocks) { - if (cube.getBlock(pos.x, pos.y, pos.z) - .getLightValue(cube.getWorld(), pos.x, pos.y, pos.z) > 0) { - lm.checkLightFor(EnumSkyBlock.Block, pos.x, pos.y, pos.z); + ICubeLoader loader = ((ICubicWorldInternal.Server) cube.getWorld()).getCubeCache().getCubeLoader(); + + loader.cacheCubes(cube.getX(), cube.getY(), cube.getZ(), 1, 1, 1); + + Box allBlocks = new Box(minPos.x, minPos.y, minPos.z, maxPos.x, maxPos.y, maxPos.z); + for (Vector3ic v : allBlocks) { + if (cube.getBlock(v.x(), v.y(), v.z()) + .getLightValue(cube.getWorld(), v.x(), v.y(), v.z()) > 0) { + lm.checkLightFor(EnumSkyBlock.Block, v.x(), v.y(), v.z()); } } + + loader.uncacheCubes(); + if (cube.getWorld().provider.hasNoSky) { return; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/lighting/phosphor/LightingHooks.java b/src/main/java/com/cardinalstar/cubicchunks/lighting/phosphor/LightingHooks.java index 3212430d..458075a2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/lighting/phosphor/LightingHooks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/lighting/phosphor/LightingHooks.java @@ -23,7 +23,7 @@ import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.cardinalstar.cubicchunks.world.cube.ICubeProvider; -import com.gtnewhorizon.gtnhlib.client.renderer.quad.Axis; +import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.properties.ModelQuadFacing.Axis; public class LightingHooks { diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index 0ec8ff88..e55280d2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -75,6 +75,12 @@ public enum Mixins implements IMixins { MIXIN_EBS(new MixinBuilder("Add simple cache to ExtendedBlockStorage.getBlockByExtId") .addCommonMixins("common.MixinExtendedBlockStorage") .setPhase(Phase.EARLY) + .addExcludedMod(Mods.NotEnoughIDs) + .addExcludedMod(Mods.ChunkAPI) + .setApplyIf(() -> true)), + ACCESSOR_S23(new MixinBuilder("Accessors for X/Y/Z fields for S23PacketBlockChange") + .addCommonMixins("common.AccessorS23PacketBlockChange") + .setPhase(Phase.EARLY) .setApplyIf(() -> true)), // CHUNK diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/api/ICubicWorldInternal.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/api/ICubicWorldInternal.java index 7a38e5fe..743af357 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/api/ICubicWorldInternal.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/api/ICubicWorldInternal.java @@ -78,6 +78,8 @@ public interface ICubicWorldInternal extends ICubicWorld { void fakeWorldHeight(int height); + void setHeightBounds(int minHeight, int maxHeight); + default BlockPos getTopSolidOrLiquidBlockVanilla(int x, int y, int z) { Chunk chunk = ((World) this).getChunkFromBlockCoords(x, y); @@ -138,8 +140,6 @@ interface Client extends ICubicWorldInternal { void initCubicWorldClient(IntRange heightRange, IntRange generationRange); CubeProviderClient getCubeCache(); - - void setHeightBounds(int minHeight, int maxHeight); } interface CompatGenerationScope extends AutoCloseable { diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java index 4e1b83c8..f2914a1e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinWorldClient.java @@ -68,12 +68,6 @@ public CubeProviderClient getCubeCache() { return (CubeProviderClient) this.clientChunkProvider; } - @Override - public void setHeightBounds(int minHeight1, int maxHeight1) { - this.minHeight = minHeight1; - this.maxHeight = maxHeight1; - } - // Has to be in here because the world is intialized before initCubicWorldClient gets called. This causes a crash in // prepare spawn location on the client. @Redirect( diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/AccessorS23PacketBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/AccessorS23PacketBlockChange.java new file mode 100644 index 00000000..0909bc54 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/AccessorS23PacketBlockChange.java @@ -0,0 +1,28 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.network.play.server.S23PacketBlockChange; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(S23PacketBlockChange.class) +public interface AccessorS23PacketBlockChange { + + @Accessor("field_148887_a") + int getX(); + + @Accessor("field_148885_b") + int getY(); + + @Accessor("field_148886_c") + int getZ(); + + @Accessor("field_148887_a") + void setX(int x); + + @Accessor("field_148885_b") + void setY(int y); + + @Accessor("field_148886_c") + void setZ(int z); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java index 2f7e2188..028d45b0 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinChunk_Cubes.java @@ -68,6 +68,7 @@ import com.cardinalstar.cubicchunks.api.IHeightMap; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.api.IMinMaxHeight; import com.cardinalstar.cubicchunks.world.column.ColumnTileEntityMap; import com.cardinalstar.cubicchunks.world.column.CubeMap; @@ -267,7 +268,9 @@ private void cubicChunkColumn_construct(World world, int x, int z, CallbackInfo // this.chunkSections = null; // this.skylightUpdateMap = null; - Arrays.fill(getBiomeArray(), (byte) -1); + if (!Mods.ChunkAPI.isModLoaded()) { + Arrays.fill(getBiomeArray(), (byte) -1); + } } @Redirect( @@ -702,8 +705,10 @@ private void replacedGetSavedLightValueForCC(EnumSkyBlock type, int x, int y, in if (!isColumn) { return; } - ((ICubicWorldInternal) worldObj).getLightingManager() - .onGetLight(type, x, y, z); + if (((ICubicWorldInternal) worldObj).getLightingManager() != null) { + ((ICubicWorldInternal) worldObj).getLightingManager() + .onGetLight(type, x, y, z); + } cir.setReturnValue(((Cube) ((IColumn) this).getCube(blockToCube(y))).getCachedLightFor(type, x, y, z)); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java index d484c684..04335a5b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java @@ -32,6 +32,8 @@ import net.minecraft.block.Block; import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.profiler.Profiler; import net.minecraft.tileentity.TileEntity; @@ -73,6 +75,8 @@ import com.cardinalstar.cubicchunks.api.world.ICubicWorldType; import com.cardinalstar.cubicchunks.lighting.LightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.network.PacketEncoderWorldHeight; +import com.cardinalstar.cubicchunks.network.PacketEncoderWorldHeight.PacketWorldHeight; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.ReflectionUtil; @@ -379,6 +383,26 @@ public int getHeight() { return this.provider.getHeight(); } + @Override + public void setHeightBounds(int minHeight, int maxHeight) { + if (minHeight >= this.minGenerationHeight && maxHeight <= this.maxHeight) return; + + this.minHeight = minHeight; + this.maxHeight = maxHeight; + + if (!this.isRemote) { + CubicChunksSavedData savedData = CubicChunksSavedData.get((World) (Object) this); + savedData.minHeight = minHeight; + savedData.maxHeight = maxHeight; + + PacketWorldHeight packet = PacketEncoderWorldHeight.create(minHeight, maxHeight); + + for (EntityPlayer player : this.playerEntities) { + packet.sendToPlayer((EntityPlayerMP) player); + } + } + } + @Inject(method = "updateLightByType", at = @At("HEAD"), cancellable = true) private void updateLightByType(EnumSkyBlock lightType, int x, int y, int z, CallbackInfoReturnable ci) { ci.setReturnValue(getLightingManager() != null && getLightingManager().checkLightFor(lightType, x, y, z)); @@ -411,6 +435,9 @@ private void onMarkChunkDirty(int x, int y, int z, TileEntity unusedTileEntity, @Shadow public abstract Block getBlock(int x, int y, int z); + @Shadow + public List playerEntities; + /** * @param x block x position * @param y block y position diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java index 3c176a87..760c5f65 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java @@ -321,9 +321,7 @@ private void updateBlocksCubicChunks(CallbackInfo cbi) { boolean thundering = this.isThundering(); this.theProfiler.startSection("pollingChunks"); - // CubicChunks - iterate over PlayerCubeMap.TickableChunkContainer instead of Chunks, getTickableChunks already - // includes forced chunks - for (Chunk chunk : ((CubicPlayerManager) this.thePlayerManager).getColumnsToTick()) { + for (Chunk chunk : ((CubeProviderServer) this.theChunkProviderServer).getTickableChunks()) { tickColumn(raining, thundering, chunk); } @@ -331,9 +329,9 @@ private void updateBlocksCubicChunks(CallbackInfo cbi) { long worldTime = worldInfo.getWorldTotalTime(); -// for (Cube cube : ((CubeProviderServer) this.theChunkProviderServer).getTickableCubes()) { -// tickCube(cube, worldTime); -// } + for (Cube cube : ((CubeProviderServer) this.theChunkProviderServer).getTickableCubes()) { + tickCube(cube, worldTime); + } this.theProfiler.endSection(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java index b681b1a9..a6532a36 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java @@ -2,25 +2,18 @@ import java.util.Random; -import net.minecraft.block.Block; import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.World; -import net.minecraft.world.chunk.IChunkProvider; import net.minecraft.world.gen.ChunkProviderGenerate; -import net.minecraft.world.gen.MapGenBase; import net.minecraft.world.gen.NoiseGeneratorOctaves; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; import com.cardinalstar.cubicchunks.api.world.Precalculable; import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalcedVanillaOctaves; import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalculableNoise; -import com.llamalad7.mixinextras.expression.Definition; -import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; @@ -48,18 +41,6 @@ public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves, Ope return new PrecalcedVanillaOctaves(original.call(random, octaves)); } - @Definition( - id = "caveGenerator", - field = "Lnet/minecraft/world/gen/ChunkProviderGenerate;caveGenerator:Lnet/minecraft/world/gen/MapGenBase;") - @Definition( - id = "func_151539_a", - method = "Lnet/minecraft/world/gen/MapGenBase;func_151539_a(Lnet/minecraft/world/chunk/IChunkProvider;Lnet/minecraft/world/World;II[Lnet/minecraft/block/Block;)V") - @Expression("this.caveGenerator.func_151539_a(?, ?, ?, ?, ?)") - @Redirect(method = "provideChunk", at = @At("MIXINEXTRAS:EXPRESSION")) - public void noopCaveGen(MapGenBase instance, IChunkProvider i2, World k1, int j1, int i, Block[] p_151539_1_) { - - } - @Unique private final LongArrayFIFOQueue chunkGenFIFO = new LongArrayFIFOQueue(); @Unique diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java index 78a3128d..c1896e88 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java @@ -1,6 +1,7 @@ package com.cardinalstar.cubicchunks.network; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -14,6 +15,7 @@ import com.cardinalstar.cubicchunks.util.CubePos; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; public class CCPacketBuffer extends PacketBuffer { @@ -131,6 +133,12 @@ public void writeByteArray(byte[] array) { writeBytes(array); } + public void writeByteArray(byte[] array, int offset, int length) { + writeVarIntToBuffer(length); + + writeBytes(array, offset, length); + } + public byte[] readByteArray() { byte[] out = new byte[readVarIntFromBuffer()]; @@ -139,6 +147,37 @@ public byte[] readByteArray() { return out; } + public byte[] readByteArray(byte[] cached) { + int len = readVarIntFromBuffer(); + byte[] out = len < cached.length ? cached : new byte[len]; + + readBytes(out, 0, len); + + return out; + } + + public void writeByteBuf(ByteBuf buffer) { + byte[] data = new byte[buffer.readableBytes()]; + buffer.readBytes(data); + + writeByteArray(data); + } + + public ByteBuf readByteBuf() { + return Unpooled.wrappedBuffer(readByteArray()); + } + + public void writeByteBuffer(ByteBuffer buffer) { + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + + writeByteArray(data); + } + + public ByteBuffer readByteBuffer() { + return ByteBuffer.wrap(readByteArray()); + } + public void writeIntArray(int[] array) { writeVarIntToBuffer(array.length); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEncoder.java b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEncoder.java index f4bd333c..a8ecf893 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEncoder.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEncoder.java @@ -15,19 +15,25 @@ protected CCPacketEncoder() {} /** * Encode the data into given byte buffer. */ - public abstract void writePacket(CCPacketBuffer buffer, Packet packet); + public void writePacket(CCPacketBuffer buffer, Packet packet) { + throw new UnsupportedOperationException("Wrong side"); + } /** * Decode byte buffer into packet object. */ - public abstract Packet readPacket(CCPacketBuffer buffer); + public Packet readPacket(CCPacketBuffer buffer) { + throw new UnsupportedOperationException("Wrong side"); + } /** * Process the received packet. * * @param world null if message is received on server side, the client world if message is received on client side */ - public abstract void process(World world, Packet packet); + public void process(World world, Packet packet) { + throw new UnsupportedOperationException("Wrong side"); + } /** * This will be called just before {@link #process(World, CCPacket)}} to inform the handler about the source and diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEntry.java b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEntry.java index 516d7501..2e3b6bf9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEntry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketEntry.java @@ -8,7 +8,10 @@ public enum CCPacketEntry { UnloadCube(new PacketEncoderUnloadCube()), CubeBlockChange(new PacketEncoderCubeBlockChange()), HeightMapUpdate(new PacketEncoderHeightMapUpdate()), - CubeSkyLightUpdates(new PacketEncoderCubeSkyLightUpdates()),; + CubeSkyLightUpdates(new PacketEncoderCubeSkyLightUpdates()), + WorldHeight(new PacketEncoderWorldHeight()), + // + ; public final byte id = (byte) ordinal(); public final CCPacketEncoder encoder; diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index 73e41cb4..fa65f7fb 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -20,22 +20,32 @@ */ package com.cardinalstar.cubicchunks.network; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.List; + import javax.annotation.ParametersAreNonnullByDefault; -import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.network.play.server.S23PacketBlockChange; import net.minecraft.world.World; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.client.CubeProviderClient; +import com.cardinalstar.cubicchunks.mixin.early.common.AccessorS23PacketBlockChange; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.falsepattern.chunk.internal.DataRegistryImpl; import com.github.bsideup.jabel.Desugar; -import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import gnu.trove.iterator.TIntIterator; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; @@ -44,8 +54,7 @@ public class PacketEncoderCubeBlockChange extends CCPacketEncoder { @Desugar - public record PacketCubeBlockChange(CubePos cubePos, short[] localAddresses, Block[] blocks, int[] blockMetas, - int[] heightValues) implements CCPacket { + public record PacketCubeBlockChange(CubePos cubePos, int[] heightValues, List updates) implements CCPacket { @Override public byte getPacketID() { @@ -55,12 +64,11 @@ public byte getPacketID() { public PacketEncoderCubeBlockChange() {} + @SuppressWarnings("DataFlowIssue") public static PacketCubeBlockChange createPacket(Cube cube, short[] localAddresses) { CubePos cubePos = cube.getCoords(); - Block[] blocks = new Block[localAddresses.length]; - int[] blockMetas = new int[localAddresses.length]; - + List updates = new ArrayList<>(localAddresses.length); TIntSet xzAddresses = new TIntHashSet(); for (int i = 0, localAddressesLength = localAddresses.length; i < localAddressesLength; i++) { @@ -70,8 +78,24 @@ public static PacketCubeBlockChange createPacket(Cube cube, short[] localAddress int y = AddressTools.getLocalY(localAddress); int z = AddressTools.getLocalZ(localAddress); - blocks[i] = cube.getBlock(x, y, z); - blockMetas[i] = cube.getBlockMetadata(x, y, z); + S23PacketBlockChange change = new S23PacketBlockChange(); + + int wX = (cube.getX() << 4) + x; + int wY = (cube.getY() << 4) + y; + int wZ = (cube.getZ() << 4) + z; + + ((AccessorS23PacketBlockChange) change).setX(wX); + ((AccessorS23PacketBlockChange) change).setY(wY); + ((AccessorS23PacketBlockChange) change).setZ(wZ); + + change.field_148883_d = cube.getBlock(x, y, z); + change.field_148884_e = cube.getBlockMetadata(x, y, z); + + if (Mods.ChunkAPI.isModLoaded()) { + DataRegistryImpl.writeBlockToPacket(cube.getColumn(), wX, wY, wZ, change); + } + + updates.add(change); xzAddresses.add(AddressTools.getLocalAddress(x, z)); } @@ -90,7 +114,7 @@ public static PacketCubeBlockChange createPacket(Cube cube, short[] localAddress i++; } - return new PacketCubeBlockChange(cubePos, localAddresses, blocks, blockMetas, heightValues); + return new PacketCubeBlockChange(cubePos, heightValues, updates); } @Override @@ -101,49 +125,39 @@ public byte getPacketID() { @Override public void writePacket(CCPacketBuffer buffer, PacketCubeBlockChange packet) { buffer.writeCubePos(packet.cubePos); - - buffer.writeShort(packet.localAddresses.length); - - for (int i = 0; i < packet.localAddresses.length; i++) { - buffer.writeShort(packet.localAddresses[i]); - buffer.writeBlock(packet.blocks[i]); - buffer.writeBlockMeta(packet.blockMetas[i]); - } - - buffer.writeByte(packet.heightValues.length); - - for (int v : packet.heightValues) { - buffer.writeInt(v); - } + buffer.writeIntArray(packet.heightValues); + + buffer.writeList(packet.updates, (buffer1, value) -> { + try { + value.writePacketData(buffer1); + } catch (IOException e) { + throw new UncheckedIOException("Could not save S23PacketBlockChange " + value.serialize(), e); + } + }); } @Override public PacketCubeBlockChange readPacket(CCPacketBuffer buffer) { CubePos pos = buffer.readCubePos(); + int[] heightValues = buffer.readIntArray(); - int blockCount = buffer.readShort(); + List updates = buffer.readList(buffer1 -> { + S23PacketBlockChange packet = new S23PacketBlockChange(); - short[] addresses = new short[blockCount]; - Block[] blocks = new Block[blockCount]; - int[] metas = new int[blockCount]; - - for (short i = 0; i < blockCount; i++) { - addresses[i] = buffer.readShort(); - blocks[i] = buffer.readBlock(); - metas[i] = buffer.readBlockMeta(); - } + try { + packet.readPacketData(buffer1); + } catch (IOException e) { + throw new UncheckedIOException(e); + } - int heightmapCount = buffer.readUnsignedByte(); - int[] heightValues = new int[heightmapCount]; + return packet; + }); - for (int i = 0; i < heightmapCount; i++) { - heightValues[i] = buffer.readInt(); - } - - return new PacketCubeBlockChange(pos, addresses, blocks, metas, heightValues); + return new PacketCubeBlockChange(pos, heightValues, updates); } @Override +@SideOnly(Side.CLIENT) public void process(World world, PacketCubeBlockChange packet) { WorldClient worldClient = (WorldClient) world; CubeProviderClient cubeCache = (CubeProviderClient) worldClient.getChunkProvider(); @@ -157,6 +171,7 @@ public void process(World world, PacketCubeBlockChange packet) { ClientHeightMap index = (ClientHeightMap) cube.getColumn() .getOpacityIndex(); + for (int hmapUpdate : packet.heightValues) { int x = hmapUpdate & 0xF; int z = (hmapUpdate >> 4) & 0xF; @@ -164,12 +179,9 @@ public void process(World world, PacketCubeBlockChange packet) { int height = hmapUpdate >> 8; index.setHeight(x, z, height); } - // apply the update - for (int i = 0; i < packet.localAddresses.length; i++) { - BlockPos pos = cube.localAddressToBlockPos(packet.localAddresses[i]); - worldClient - .invalidateBlockReceiveRegion(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ()); - worldClient.setBlock(pos.getX(), pos.getY(), pos.getZ(), packet.blocks[i], packet.blockMetas[i], 3); + + for (S23PacketBlockChange update : packet.updates) { + update.processPacket(Minecraft.getMinecraft().thePlayer.sendQueue); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index 823dc9fd..3d0cbb01 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -81,7 +81,7 @@ public static PacketCubes createPacket(List cubes) { List> tileEntityTags = new ArrayList<>(); - cubes.forEach(cube -> { + for (Cube cube : cubes) { if (cube.getTileEntityMap() .isEmpty()) { tileEntityTags.add(Collections.emptyList()); @@ -97,7 +97,7 @@ public static PacketCubes createPacket(List cubes) { tileEntityTags.add(list); } - }); + } return new PacketCubes(cubePos, data, tileEntityTags); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderWorldHeight.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderWorldHeight.java new file mode 100644 index 00000000..fa295c4d --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderWorldHeight.java @@ -0,0 +1,44 @@ +package com.cardinalstar.cubicchunks.network; + +import net.minecraft.world.World; + +import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.network.PacketEncoderWorldHeight.PacketWorldHeight; +import com.github.bsideup.jabel.Desugar; + +public class PacketEncoderWorldHeight extends CCPacketEncoder { + + @Desugar + public record PacketWorldHeight(int min, int max) implements CCPacket { + + @Override + public byte getPacketID() { + return CCPacketEntry.WorldHeight.id; + } + } + + public static PacketWorldHeight create(int min, int max) { + return new PacketWorldHeight(min, max); + } + + @Override + public byte getPacketID() { + return CCPacketEntry.WorldHeight.id; + } + + @Override + public void writePacket(CCPacketBuffer buffer, PacketWorldHeight packet) { + buffer.writeInt(packet.min); + buffer.writeInt(packet.max); + } + + @Override + public PacketWorldHeight readPacket(CCPacketBuffer buffer) { + return new PacketWorldHeight(buffer.readInt(), buffer.readInt()); + } + + @Override + public void process(World world, PacketWorldHeight packet) { + ((ICubicWorldInternal) world).setHeightBounds(packet.min, packet.max); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java b/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java index 7f311492..3074f16b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.ParametersAreNonnullByDefault; @@ -30,170 +29,177 @@ import net.minecraft.world.chunk.NibbleArray; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import org.jetbrains.annotations.NotNull; + +import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.lighting.ILightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.falsepattern.chunk.internal.DataRegistryImpl; -// TODO Watch implementation packet io functions for block data arrays and serialization length @ParametersAreNonnullByDefault class WorldEncoder { + private static final ThreadLocal BUFFER = new ThreadLocal<>(); + static void encodeColumn(CCPacketBuffer out, Chunk column) { - // 1. biomes - out.writeBytes(column.getBiomeArray()); + if (!Mods.ChunkAPI.isModLoaded()) { + // 1. biomes + out.writeBytes(column.getBiomeArray()); + } else { + byte[] buffer = getBuffer(DataRegistryImpl.maxPacketSize()); + + int written = DataRegistryImpl.writeToBuffer(column, 0, true, buffer); + + out.writeByteArray(buffer, 0, written); + } + ((IColumnInternal) column).writeHeightmapDataForClient(out); } static void decodeColumn(CCPacketBuffer in, Chunk column) { - // 1. biomes - in.readBytes(column.getBiomeArray()); + if (!Mods.ChunkAPI.isModLoaded()) { + // 1. biomes + in.readBytes(column.getBiomeArray()); + } else { + byte[] buffer = in.readByteArray(getBuffer(DataRegistryImpl.maxPacketSize())); + + DataRegistryImpl.readFromBuffer(column, 0, true, buffer); + } + if (in.readableBytes() > 0) { ((IColumnInternal) column).loadClientHeightmapData(in); } } static void encodeCubes(CCPacketBuffer out, Collection cubes) { - // write first all the flags, then all the block data, then all the light data etc for better compression + final boolean capi = Mods.ChunkAPI.isModLoaded(); + + byte[] buffer = new byte[0]; // getBuffer(DataRegistryImpl.maxPacketSizeCubic()); + + for (Cube cube : cubes) { + ExtendedBlockStorage storage = cube.getStorage(); + + boolean empty = storage == null || cube.isEmpty(); - // 1. emptiness - cubes.forEach(cube -> { byte flags = 0; - if (cube.isEmpty()) flags |= 1; - if (cube.getStorage() != null) flags |= 2; - if (cube.getBiomeArray() != null) flags |= 4; + if (empty) flags |= 0b1; + + // 1. emptiness out.writeByte(flags); - }); - // 2. block IDs and metadata - cubes.forEach(cube -> { - if (!cube.isEmpty()) { - // noinspection ConstantConditions - ExtendedBlockStorage storage = cube.getStorage(); + if (!empty && !capi) { + // 2. block IDs and metadata out.writeBytes(storage.getBlockLSBArray()); NibbleArray msb = storage.getBlockMSBArray(); + out.writeBoolean(msb != null); if (msb != null) { out.writeBytes(msb.data); } + out.writeBytes(storage.getMetadataArray().data); - } - }); - // 3. block light - cubes.forEach(cube -> { - ExtendedBlockStorage storage = cube.getStorage(); - if (storage != null) { + // 3. block light out.writeBytes(storage.getBlocklightArray().data); - } - }); - // 4. sky light - cubes.forEach(cube -> { - ExtendedBlockStorage storage = cube.getStorage(); - if (storage != null && !cube.getWorld().provider.hasNoSky) { - out.writeBytes(storage.getSkylightArray().data); + // 4. sky light + if (!cube.getWorld().provider.hasNoSky) { + out.writeBytes(storage.getSkylightArray().data); + } } - }); - - // 5. heightmap and bottom-block-y. Each non-empty cube has a chance - // to update this data. - // trying to keep track of when it changes would be complex, so send - // it wil all cubes - cubes.forEach(cube -> { - if (!cube.isEmpty()) { + + // 5. heightmap and bottom-block-y. Each non-empty cube has a chance to update this data. + // Trying to keep track of when it changes would be complex, so send all cubes + if (!empty) { ((IColumnInternal) cube.getColumn()).writeHeightmapDataForClient(out); } - }); - // 6. biomes - cubes.forEach(cube -> { if (cube.getBiomeArray() != null) out.writeBytes(cube.getBiomeArray()); }); + // 6. biomes + cube.writeBiomeArray(out); + + if (!empty && capi) { + // int written = DataRegistryImpl.writeToBufferCubic(cube.getColumn(), storage, buffer); + + // out.writeByteArray(buffer, 0, written); + } + } + } + + private static byte @NotNull [] getBuffer(int maxSize) { + byte[] buffer = BUFFER.get(); + + if (buffer == null || buffer.length < maxSize) { + buffer = new byte[maxSize]; + BUFFER.set(buffer); + } + + return buffer; } static void decodeCube(CCPacketBuffer in, List cubes) { - cubes.stream() - .filter(Objects::nonNull) - .forEach(Cube::setClientCube); + final boolean capi = Mods.ChunkAPI.isModLoaded(); + + int[] oldHeights = new int[Cube.SIZE * Cube.SIZE]; - // 1. emptiness - boolean[] isEmpty = new boolean[cubes.size()]; - boolean[] hasStorage = new boolean[cubes.size()]; - boolean[] hasCustomBiomeMap = new boolean[cubes.size()]; + byte[] buffer = new byte[0]; // getBuffer(DataRegistryImpl.maxPacketSizeCubic()); for (int i = 0; i < cubes.size(); i++) { + Cube cube = cubes.get(i); + + if (cube == null) continue; + + cube.setClientCube(); + byte flags = in.readByte(); - isEmpty[i] = (flags & 1) != 0 || cubes.get(i) == null; - hasStorage[i] = (flags & 2) != 0 && cubes.get(i) != null; - hasCustomBiomeMap[i] = (flags & 4) != 0 && cubes.get(i) != null; - } - for (int i = 0; i < cubes.size(); i++) { - if (hasStorage[i]) { - Cube cube = cubes.get(i); - ExtendedBlockStorage storage = new ExtendedBlockStorage( + boolean empty = (flags & 0b1) != 0; + + ExtendedBlockStorage storage = null; + + if (!empty) { + storage = new ExtendedBlockStorage( Coords.cubeToMinBlock(cube.getY()), !cube.getWorld().provider.hasNoSky); - cube.setStorageFromSave(storage); } - } - // 2. Block IDs and metadata - for (int i = 0; i < cubes.size(); i++) { - if (!isEmpty[i]) { - // noinspection ConstantConditions - ExtendedBlockStorage storage = cubes.get(i) - .getStorage(); - if (storage != null) { - byte[] lsbData = storage.getBlockLSBArray(); - in.readBytes(lsbData); - - boolean hasMsb = in.readBoolean(); - if (hasMsb) { - if (storage.getBlockMSBArray() == null) { - storage.createBlockMSBArray(); - } + cube.setStorageFromSave(storage); + + if (!empty && !capi) { + // 2. Block IDs and metadata + byte[] lsbData = storage.getBlockLSBArray(); + in.readBytes(lsbData); - byte[] msbData = storage.getBlockMSBArray().data; - in.readBytes(msbData); + boolean hasMsb = in.readBoolean(); + if (hasMsb) { + if (storage.getBlockMSBArray() == null) { + storage.createBlockMSBArray(); } - byte[] meta = storage.getMetadataArray().data; - in.readBytes(meta); + + byte[] msbData = storage.getBlockMSBArray().data; + in.readBytes(msbData); } - } - } + byte[] meta = storage.getMetadataArray().data; + in.readBytes(meta); - // 3. block light - for (int i = 0; i < cubes.size(); i++) { - if (hasStorage[i]) { - // noinspection ConstantConditions - byte[] data = cubes.get(i) - .getStorage() - .getBlocklightArray().data; - in.readBytes(data); - } - } + // 3. block light + in.readBytes(storage.getBlocklightArray().data); - // 4. sky light - for (int i = 0; i < cubes.size(); i++) { - if (hasStorage[i] && !cubes.get(i) - .getWorld().provider.hasNoSky) { - // noinspection ConstantConditions - byte[] data = cubes.get(i) - .getStorage() - .getSkylightArray().data; - in.readBytes(data); + // 4. sky light + if (!cube.getWorld().provider.hasNoSky) { + in.readBytes(storage.getSkylightArray().data); + } } - } - int[] oldHeights = new int[Cube.SIZE * Cube.SIZE]; - // 5. heightmaps and after all that - update ref counts - for (int i = 0; i < cubes.size(); i++) { - if (!isEmpty[i]) { - Cube cube = cubes.get(i); + if (!empty) { + // 5. heightmaps and after all that - update ref counts ILightingManager lm = ((ICubicWorldInternal) cube.getWorld()).getLightingManager(); + IColumnInternal column = cube.getColumn(); ClientHeightMap coi = (ClientHeightMap) column.getOpacityIndex(); for (int dx = 0; dx < Cube.SIZE; dx++) { @@ -201,7 +207,9 @@ static void decodeCube(CCPacketBuffer in, List cubes) { oldHeights[AddressTools.getLocalAddress(dx, dz)] = coi.getTopBlockY(dx, dz); } } + column.loadClientHeightmapData(in); + for (int dx = 0; dx < Cube.SIZE; dx++) { for (int dz = 0; dz < Cube.SIZE; dz++) { int oldY = oldHeights[AddressTools.getLocalAddress(dx, dz)]; @@ -211,19 +219,22 @@ static void decodeCube(CCPacketBuffer in, List cubes) { } } } - // noinspection ConstantConditions - cube.getStorage() - .removeInvalidBlocks(); } - } - // 6. biomes - for (int i = 0; i < cubes.size(); i++) { - if (!hasCustomBiomeMap[i]) continue; - Cube cube = cubes.get(i); - byte[] blockBiomeArray = new byte[Coords.BIOMES_PER_CUBE]; - in.readBytes(blockBiomeArray); - cube.setBiomeArray(blockBiomeArray); + // 6. biomes + cube.readBiomeArray(in); + + if (!empty && capi) { + try { + // DataRegistryImpl.readFromBufferCubic(cube.getColumn(), storage, in.readByteArray(buffer)); + } catch (Throwable t) { + CubicChunks.LOGGER.error("Error decoding ChunkAPI data ({},{},{})", cube.getX(), cube.getY(), cube.getZ(), t); + } + } + + if (!empty) { + storage.removeInvalidBlocks(); + } } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index 3472c92b..79445f79 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -107,9 +107,6 @@ public class CubeProviderServer extends ChunkProviderServer private final Map eagerLoads = new Object2ObjectOpenHashMap<>(); private final List eagerLoadOrder = new ArrayList<>(); - private int columnsLoadedThisTick = 0; - private int cubesLoadedThisTick = 0; - private static final int MAX_NS_SPENT_LOADING = 10_000_000; private final ListMultimap pendingAsyncChunkLoads = MultimapBuilder.hashKeys() @@ -167,8 +164,6 @@ public void onColumnLoaded(Chunk column) { pendingAsyncChunkLoads.removeAll(new ChunkCoordIntPair(column.xPosition, column.zPosition)) .forEach(Runnable::run); - columnsLoadedThisTick++; - callbacks.forEach(c -> c.onColumnLoaded(column)); } @@ -183,15 +178,11 @@ public void onColumnUnloaded(Chunk column) { @Override public void onCubeLoaded(Cube cube) { - cubesLoadedThisTick++; - callbacks.forEach(c -> c.onCubeLoaded(cube)); } @Override public void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { - cubesLoadedThisTick++; - callbacks.forEach(c -> c.onCubeGenerated(cube, newLevel)); } @@ -328,9 +319,6 @@ public void tick() { getCubeLoader().setNow(worldObj.getTotalWorldTime()); doEagerLoading(); - - columnsLoadedThisTick = 0; - cubesLoadedThisTick = 0; } private void doEagerLoading() { @@ -343,6 +331,9 @@ private void doEagerLoading() { long start = System.nanoTime(); + int processed = 0; + int startCols = eagerLoadOrder.size(); + while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && !eagerLoadOrder.isEmpty()) { ChunkCoordIntPair coord = eagerLoadOrder.get(eagerLoadOrder.size() - 1); @@ -362,15 +353,9 @@ private void doEagerLoading() { cubeIter.remove(); request.completed = true; - if (request.isCancelled()) continue; - - cubeLoader.preloadCube(request.pos, CubeInitLevel.fromRequirement(request.effort)); + processed++; - try { - Thread.sleep(1); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + if (request.isCancelled()) continue; cubeLoader.pauseLoadCalls(); @@ -406,9 +391,20 @@ private void doEagerLoading() { CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); } + if (processed > 0) { + CubicChunks.LOGGER.info("Processed {} eager load requests this tick ({} -> {} columns)", + processed, + startCols, + eagerLoadOrder.size()); + } + profiler.endSection(); } + public Collection getTickableChunks() { + return ((CubicPlayerManager) worldServer.getPlayerManager()).getColumns(); + } + public Collection getTickableCubes() { return ((CubicPlayerManager) worldServer.getPlayerManager()).getCubes(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index f7e1567f..bd108410 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -26,7 +26,6 @@ import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -130,32 +129,33 @@ public CubicPlayerManager(WorldServer worldServer) { provider.registerCallback(this); } - public Iterable getColumnsToTick() { - // TODO: this - return Collections.emptyList(); - } + public Collection getColumns() { + return new AbstractCollection<>() { - public Iterable getCubesToTick() { - // TODO: this - return Collections.emptyList(); - } + @Override + public @NotNull Iterator iterator() { + return new AbstractIterator<>() { - public Iterable getWatchedColumns() { - return () -> new AbstractIterator<>() { + final Iterator iter = watchedColumns.iterator(); - final Iterator iter = watchedColumns.iterator(); + @Override + protected Chunk computeNext() { + while (iter.hasNext()) { + WatchedColumn column = iter.next(); - @Override - protected Chunk computeNext() { - while (iter.hasNext()) { - WatchedColumn column = iter.next(); + if (column.column == null) continue; - if (column.column == null || column.watchingPlayers.isEmpty()) continue; + return column.column; + } - return column.column; - } + return this.endOfData(); + } + }; + } - return this.endOfData(); + @Override + public int size() { + return watchedColumns.getSize(); } }; } @@ -267,6 +267,8 @@ private void syncCubes() { List fullSync = new ObjectArrayList<>(); + List notReady = new ArrayList<>(); + for (WatchedCube cube : dirtyCubes) { if (cube.cube == null) continue; @@ -279,6 +281,13 @@ private void syncCubes() { cube.clean(); } case Full -> { + WatchedColumn col = watchedColumns.get(cube.getX(), cube.getZ()); + + if (col == null || col.column == null) { + notReady.add(cube); + continue; + } + fullSync.add(cube); } } @@ -287,6 +296,7 @@ private void syncCubes() { } dirtyCubes.clear(); + dirtyCubes.addAll(notReady); for (WatchedCube cube : fullSync) { for (WatchingPlayer player : cube.watchingPlayers) { @@ -303,7 +313,7 @@ private void syncCubes() { @Override public void onColumnLoaded(Chunk column) { - WatchedColumn watcher = this.watchedColumns.get(column.xPosition, column.zPosition); + WatchedColumn watcher = this.getOrCreateWatchedColumn(new ChunkCoordIntPair(column.xPosition, column.zPosition)); if (watcher != null) { watcher.setColumn(column); @@ -975,6 +985,7 @@ public void removePlayer(WatchingPlayer player) { public void clean() { this.dirtyBlocks.clear(); this.dirty = Dirtiness.None; + CubeStatusVisualizer.put(new CubePos(this), CubeStatus.Synced); } private void syncPartial() { @@ -988,9 +999,7 @@ private void syncPartial() { int blockY = this.cube.getY() << 4; int blockZ = this.cube.getZ() << 4; - for (int i = 0, localAddressesLength = dirtyBlocks.length; i < localAddressesLength; i++) { - short localAddress = dirtyBlocks[i]; - + for (short localAddress : dirtyBlocks) { int x = AddressTools.getLocalX(localAddress); int y = AddressTools.getLocalY(localAddress); int z = AddressTools.getLocalZ(localAddress); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index 32f6645a..4ea880f5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -1,21 +1,20 @@ package com.cardinalstar.cubicchunks.server.chunkio; import java.io.IOException; -import java.util.ArrayList; +import java.time.Duration; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.Future; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.storage.IThreadedFileIO; -import net.minecraft.world.storage.ThreadedFileIOBase; import net.minecraftforge.common.MinecraftForge; +import org.jetbrains.annotations.Nullable; + import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage; import com.cardinalstar.cubicchunks.api.world.storage.ICubicStorage.PosBatch; @@ -26,38 +25,42 @@ import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.DataUtils; import com.cardinalstar.cubicchunks.world.cube.Cube; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import it.unimi.dsi.fastutil.Pair; +import com.github.bsideup.jabel.Desugar; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +public class CubeIO implements ICubeIO { -public class CubeIO implements ICubeIO, IThreadedFileIO { + private static final long EXPIRY = Duration.ofSeconds(120).toMillis(); private final ICubicStorage storage; private final IPreloadFailureDelegate preloadFailures; private final ITaskExecutor> columnLoadExecutor; private final ITaskExecutor> cubeLoadExecutor; + private final ITaskExecutor saveExecutor; - private final LinkedTransferQueue> columnQueue = new LinkedTransferQueue<>(); - private final LinkedTransferQueue> cubeQueue = new LinkedTransferQueue<>(); + private final Object2ObjectLinkedOpenHashMap columnCache = new Object2ObjectLinkedOpenHashMap<>(); - private final Map pendingColumns = new ConcurrentHashMap<>(); - private final Map pendingCubes = new ConcurrentHashMap<>(); + private final Object2ObjectLinkedOpenHashMap cubeCache = new Object2ObjectLinkedOpenHashMap<>(); - @SuppressWarnings("unchecked") - private final Cache> columnCache = ((CacheBuilder>) (Object) CacheBuilder - .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) - .softValues() - .initialCapacity(512) - .maximumSize(4096) - .build(); + interface Savable { } - @SuppressWarnings("unchecked") - private final Cache> cubeCache = ((CacheBuilder>) (Object) CacheBuilder - .newBuilder()).expireAfterAccess(60, TimeUnit.SECONDS) - .softValues() - .initialCapacity(1024) - .maximumSize(8192) - .build(); + @Desugar + private record SaveColumn(ChunkCoordIntPair pos, NBTTagCompound tag) implements Savable { } + @Desugar + private record SaveCube(CubePos pos, NBTTagCompound tag) implements Savable { } + + @NoArgsConstructor + @AllArgsConstructor + private static class SaveData { + @Nullable + public NBTTagCompound tag; + @Nullable + public Future task; + public long lastAccess; + } public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { this.storage = storage; @@ -100,13 +103,58 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { } } }; + + saveExecutor = new ITaskExecutor<>() { + + @Override + public void execute(List> tasks) { + Map columns = new Object2ObjectOpenHashMap<>(); + Map cubes = new Object2ObjectOpenHashMap<>(); + + for (ITaskFuture task : tasks) { + Savable savable = task.getTask(); + + if (savable instanceof SaveColumn column) { + columns.put(column.pos, column.tag); + } + + if (savable instanceof SaveCube cube) { + cubes.put(cube.pos, cube.tag); + } + } + + IOException ex = null; + + try { + storage.writeBatch(new ICubicStorage.NBTBatch(columns, cubes)); + } catch (IOException e) { + CubicChunks.LOGGER.error("Could not save columns or cubes", e); + ex = e; + } + + for (ITaskFuture task : tasks) { + if (ex != null) { + task.fail(ex); + } else { + task.finish(null); + } + } + } + + @Override + public boolean canMerge(List> tasks, Savable savable) { + return true; + } + }; } @Override public boolean columnExists(ChunkCoordIntPair pos) { - if (pendingColumns.containsKey(pos)) return true; - if (columnCache.asMap() - .containsKey(pos)) return true; + synchronized (columnCache) { + SaveData data = columnCache.get(pos); + + if (data != null) return data.tag != null; + } try { if (storage.columnExists(pos)) return true; @@ -119,9 +167,11 @@ public boolean columnExists(ChunkCoordIntPair pos) { @Override public boolean cubeExists(CubePos pos) { - if (pendingCubes.containsKey(pos)) return true; - if (cubeCache.asMap() - .containsKey(pos)) return true; + synchronized (cubeCache) { + SaveData data = cubeCache.get(pos); + + if (data != null) return data.tag != null; + } try { if (storage.cubeExists(pos)) return true; @@ -134,26 +184,22 @@ public boolean cubeExists(CubePos pos) { @Override public NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureException { - NBTTagCompound tag = pendingColumns.get(pos); - - if (tag != null) return (NBTTagCompound) tag.copy(); - - Optional cached = columnCache.getIfPresent(pos); + synchronized (columnCache) { + SaveData data = columnCache.get(pos); - if (cached != null) { - columnCache.invalidate(pos); - - if (cached.isPresent()) { - return (NBTTagCompound) cached.get().copy(); - } else { - return null; + if (data != null) { + return data.tag == null ? null : (NBTTagCompound) data.tag.copy(); } } try { - tag = storage.readColumn(pos); + NBTTagCompound tag = storage.readColumn(pos); - if (tag == null) columnCache.put(pos, Optional.empty()); + if (tag == null) { + synchronized (columnCache) { + columnCache.put(pos, new SaveData(null, null, System.currentTimeMillis())); + } + } return tag; } catch (IOException e) { @@ -164,26 +210,22 @@ public NBTTagCompound loadColumn(ChunkCoordIntPair pos) throws LoadFailureExcept @Override public NBTTagCompound loadCube(CubePos pos) throws LoadFailureException { - NBTTagCompound tag = pendingCubes.get(pos); + synchronized (cubeCache) { + SaveData data = cubeCache.get(pos); - if (tag != null) return (NBTTagCompound) tag.copy(); - - Optional cached = cubeCache.getIfPresent(pos); - - if (cached != null) { - cubeCache.invalidate(pos); - - if (cached.isPresent()) { - return (NBTTagCompound) cached.get().copy(); - } else { - return null; + if (data != null) { + return data.tag == null ? null : (NBTTagCompound) data.tag.copy(); } } try { - tag = storage.readCube(pos); + NBTTagCompound tag = storage.readCube(pos); - if (tag == null) cubeCache.put(pos, Optional.empty()); + if (tag == null) { + synchronized (cubeCache) { + cubeCache.put(pos, new SaveData(null, null, System.currentTimeMillis())); + } + } return tag; } catch (IOException e) { @@ -201,12 +243,35 @@ public void saveColumn(ChunkCoordIntPair pos, Chunk column) { // add the column to the save queue NBTTagCompound tag = IONbtWriter.write(column); - this.pendingColumns.put(pos, tag); - this.columnQueue.add(Pair.of(pos, tag)); - column.isModified = false; - ThreadedFileIOBase.threadedIOInstance.queueIO(this); + Future task = TaskPool.submit(saveExecutor, new SaveColumn(pos, tag)); + + long now = System.currentTimeMillis(); + + synchronized (columnCache) { + columnCache.put(pos, new SaveData(tag, task, now)); + + if (columnCache.size() > 5000) { + int i = 0; + + var iter = columnCache.object2ObjectEntrySet().fastIterator(); + + while (columnCache.size() > 5000 && i++ < 100) { + var e = iter.next(); + + SaveData data = e.getValue(); + + if (data.task != null && data.task.isDone()) { + data.task = null; + } + + if (data.task == null && data.lastAccess + EXPIRY < now) { + iter.remove(); + } + } + } + } } public void saveCube(CubePos pos, Cube cube) { @@ -220,81 +285,68 @@ public void saveCube(CubePos pos, Cube cube) { tag = event.tag; - this.pendingCubes.put(pos, tag); - this.cubeQueue.add(Pair.of(pos, tag)); + Future task = TaskPool.submit(saveExecutor, new SaveCube(pos, tag)); + + long now = System.currentTimeMillis(); - ThreadedFileIOBase.threadedIOInstance.queueIO(this); + synchronized (cubeCache) { + cubeCache.put(pos, new SaveData(tag, task, now)); + + if (cubeCache.size() > 30000) { + int i = 0; + + var iter = cubeCache.object2ObjectEntrySet().fastIterator(); + + while (cubeCache.size() > 30000 && i++ < 100) { + var e = iter.next(); + + SaveData data = e.getValue(); + + if (data.task != null && data.task.isDone()) { + data.task = null; + } + + if (data.task == null && data.lastAccess + EXPIRY < now) { + iter.remove(); + } + } + } + } } // only used by "/save-all flush" command @Override public void flush() throws IOException { - try { - this.drainQueueBlocking(); + TaskPool.flush(); - this.storage.flush(); - } catch (InterruptedException e) { - CubicChunks.LOGGER.catching(e); - } + this.storage.flush(); } @Override public void close() throws IOException { - try { - this.drainQueueBlocking(); + TaskPool.flush(); - this.storage.close(); - } catch (InterruptedException e) { - CubicChunks.LOGGER.catching(e); - } - } + while (true) { + synchronized (columnCache) { + if (columnCache.isEmpty()) break; - protected void drainQueueBlocking() throws InterruptedException { - // This has to submit itself to the I/O thread again, and also run in a loop, in order to avoid a potential race - // condition caused by the fact that ThreadedFileIOBase is incredibly stupid. - // Don't you think that if you're going to make an ASYNCHRONOUS executor, that you'd ensure that the code is - // ACTUALLY thread-safe? well, if you're mojang, apparently you don't. + columnCache.object2ObjectEntrySet().removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + } - do { - ThreadedFileIOBase.threadedIOInstance.queueIO(this); + Thread.yield(); + } - ThreadedFileIOBase.threadedIOInstance.waitForFinish(); - } while (!this.pendingColumns.isEmpty() || !this.pendingCubes.isEmpty()); - } + while (true) { + synchronized (cubeCache) { + if (cubeCache.isEmpty()) break; - @Override - public boolean writeNextIO() { - try { - ArrayList> columns = new ArrayList<>(); - ArrayList> cubes = new ArrayList<>(); - - // Consume all dirty cubes and columns - this.columnQueue.drainTo(columns); - this.cubeQueue.drainTo(cubes); - - Map columnMap = new ConcurrentHashMap<>(); - Map cubeMap = new ConcurrentHashMap<>(); - - // Put them back into a map - columns.forEach(p -> columnMap.put(p.left(), p.right())); - cubes.forEach(p -> cubeMap.put(p.left(), p.right())); - - // Forward all tasks to the storage at once - this.storage.writeBatch( - new ICubicStorage.NBTBatch( - Collections.unmodifiableMap(columnMap), - Collections.unmodifiableMap(cubeMap))); - - // Remove from queue using remove(key, value) in order to avoid removing entries which have been modified - // since each request was queued. - columnMap.forEach(this.pendingColumns::remove); - cubeMap.forEach(this.pendingCubes::remove); - } catch (IOException e) { - CubicChunks.LOGGER.error("Could not save chunks", e); + cubeCache.object2ObjectEntrySet().removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + } + + Thread.yield(); } - // false = ok? - return false; + this.storage.close(); } @Override @@ -303,21 +355,27 @@ public void preloadColumn(ChunkCoordIntPair pos) { if (tag == null) { if (preloadFailures != null) preloadFailures.onColumnPreloadFailed(pos); } else { - columnCache.put(pos, tag); + synchronized (columnCache) { + columnCache.put(pos, new SaveData(tag.orElse(null), null, System.currentTimeMillis())); + } } }); } @Override public void preloadCube(CubePos pos, CubeInitLevel wanted) { - if (preloadFailures != null) { - preloadFailures.onCubePreloadFailed(pos, CubeInitLevel.None, wanted); - } - TaskPool.submit(cubeLoadExecutor, pos, tag -> { CubeInitLevel actual = !tag.isPresent() ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag.get()); - cubeCache.put(pos, tag); + if (actual.ordinal() < wanted.ordinal()) { + if (preloadFailures != null) { + preloadFailures.onCubePreloadFailed(pos, CubeInitLevel.None, wanted); + } + } + + synchronized (cubeCache) { + cubeCache.put(pos, new SaveData(tag.orElse(null), null, System.currentTimeMillis())); + } }); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index efd80fc7..096bf991 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -400,44 +400,51 @@ public void close() throws IOException { cubeIO.close(); } - private void handleSideEffects(GenerationResult result) { - for (Chunk column : result.columnSideEffects) { - ColumnInfo info = columns.get(column.xPosition, column.zPosition); - - if (info != null && info.column != null) { - CubicChunks.LOGGER.warn("Worldgen side-effect replaced column at {},{}!", column.xPosition, column.zPosition); - } + private void handleSideEffects(GenerationResult result, int colX, int colZ, boolean doColumns, boolean doCubes) { + if (doColumns) { + for (Chunk column : result.columnSideEffects) { + ColumnInfo info = columns.get(column.xPosition, column.zPosition); + + if (info != null && info.column != null) { + CubicChunks.LOGGER.warn("Worldgen side-effect replaced column at {},{}!", column.xPosition, column.zPosition); + continue; + } - if (info == null) { - info = new ColumnInfo(column.xPosition, column.zPosition); - columns.put(info); - } + if (info == null) { + info = new ColumnInfo(column.xPosition, column.zPosition); + columns.put(info); + } - info.source = ObjectSource.GeneratedSideEffect; - info.column = column; + info.source = ObjectSource.GeneratedSideEffect; + info.column = column; - info.onColumnLoaded(); + info.onColumnLoaded(); + } } - for (Cube cube : result.cubeSideEffects) { - CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); + if (doCubes) { + for (Cube cube : result.cubeSideEffects) { + CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); - if (info != null && info.cube != null) { - CubicChunks.LOGGER.warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); - } + if (info != null && info.cube != null) { + CubicChunks.LOGGER.warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); + continue; + } - if (info == null) { - info = new CubeInfo(cube.getX(), cube.getY(), cube.getZ()); - info.column = columns.get(cube.getX(), cube.getZ()); - cubes.put(info); - } + if (info == null) { + info = new CubeInfo(cube.getX(), cube.getY(), cube.getZ()); + cubes.put(info); + } + + info.source = ObjectSource.GeneratedSideEffect; + info.cube = cube; + cube.setMeta(CUBE_INFO, info); - info.source = ObjectSource.GeneratedSideEffect; - info.cube = cube; - cube.setMeta(CUBE_INFO, info); + info.ensureColumn(Requirement.GET_CACHED); - info.onCubeLoaded(); - callback.onCubeGenerated(cube, cube.getInitLevel()); + info.onCubeLoaded(); + callback.onCubeGenerated(cube, cube.getInitLevel()); + } } } @@ -494,7 +501,7 @@ public boolean initialize(Requirement effort) { cubeIO.saveColumn(pos, column); - handleSideEffects(result); + handleSideEffects(result, column.xPosition, column.zPosition, true, true); return true; } @@ -597,8 +604,6 @@ public boolean initialize(Requirement effort) throws IOException { return cube != null; } - ensureColumn(); - // If we haven't already loaded the NBT tag from disk, try to load it if (tag == null) { loadNBT(); @@ -640,7 +645,15 @@ private boolean loadNBT() { } private void loadCube() throws IOException { - ensureColumn(); + // Cubes should always have a containing column unless someone's deleting files. Try to load it, or fail if the column is missing. + ensureColumn(Requirement.LOAD); + + if (this.column == null) { + CubicChunks.LOGGER.error("Tried to load a cube that did not have a saved column: it will be regenerated ({},{},{})", getX(), getY(), getZ(), new Exception()); + this.cube = null; + this.tag = null; + return; + } this.cube = IONbtReader.readCube(column.column, getX(), getY(), getZ(), tag); cube.setMeta(CUBE_INFO, this); @@ -670,21 +683,19 @@ private boolean generate(CubeInitLevel requestedInitLevel) { // If this cube hasn't been generated at all (i.e. it was never on the disk), generate it if (source == ObjectSource.None) { - ensureColumn(); - - // Column had a side effect that initialized this CubeInfo - if (isInitedTo(requestedInitLevel)) return true; - if (generating) { throw new IllegalStateException( "Cannot recursively generate a cube that is already being generated"); } + // Try to get any columns that already exist, since the generator may re-generate it if it thinks the column is missing. + ensureColumn(Requirement.LOAD); + GenerationResult result; try { this.generating = true; - result = generator.provideCube(column.column, pos.getX(), pos.getY(), pos.getZ()); + result = generator.provideCube(column == null ? null : column.column, pos.getX(), pos.getY(), pos.getZ()); } finally { this.generating = false; } @@ -692,13 +703,24 @@ private boolean generate(CubeInitLevel requestedInitLevel) { if (result == null) return false; this.cube = result.object; + this.source = ObjectSource.Generated; cube.setMeta(CUBE_INFO, this); - source = ObjectSource.Generated; + // Inject the columns, if any were generated. + handleSideEffects(result, getX(), getZ(), true, false); + // You can't generate a cube without also generating a column. Fetch it here if it's missing. + ensureColumn(Requirement.GET_CACHED); + + if (this.column == null) { + throw new IllegalStateException("Generated a cube without generating its column: this is an invalid state"); + } + + // Alert everything that this cube was generated onCubeLoaded(); - handleSideEffects(result); + // Inject the other cubes in this column + handleSideEffects(result, getX(), getZ(), false, true); } boolean generated = isInitedTo(CubeInitLevel.Generated); @@ -707,7 +729,7 @@ private boolean generate(CubeInitLevel requestedInitLevel) { if (requestedInitLevel == CubeInitLevel.Generated) return generated; if (!generated) return false; - // If this cube hasn't been populated at all, generate the required cubes and populate this cube. + // If this cube hasn't been populated at all, populate it. This generates any required cubes recursively. generator.populate(cube); boolean populated = isInitedTo(CubeInitLevel.Populated); @@ -715,12 +737,14 @@ private boolean generate(CubeInitLevel requestedInitLevel) { if (requestedInitLevel == CubeInitLevel.Populated) return populated; if (!populated) return false; + // Do the initial lighting if (!cube.isInitialLightingDone() || !cube.isSurfaceTracked()) { ((ICubicWorldInternal) world).getLightingManager() .doFirstLight(cube); cube.setInitialLightingDone(true); } + // Put the surface into the column (to update the column heightmap) as needed if (!cube.isSurfaceTracked()) { cube.trackSurface(); } @@ -729,7 +753,9 @@ private boolean generate(CubeInitLevel requestedInitLevel) { } public void onCubeLoaded() { - ensureColumn(); + if (this.column == null) { + throw new IllegalStateException("Loaded a cube without giving it a column reference: this is a bug"); + } updateInitLevel(); @@ -738,6 +764,8 @@ public void onCubeLoaded() { cube.onCubeLoad(); + CubeLoaderServer.this.generator.recreateStructures(cube); + if (pauseLoadCalls == 0) { callback.onCubeLoaded(cube); } else { @@ -767,9 +795,9 @@ public void onCubeUnloaded() { } } - private void ensureColumn() { + private void ensureColumn(Requirement effort) { if (column == null) { - column = getColumnInfo(getX(), getZ(), Requirement.GENERATE); + column = getColumnInfo(getX(), getZ(), effort); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java index 3acdf73b..f4c48708 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java @@ -38,17 +38,22 @@ import net.minecraft.world.chunk.NibbleArray; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.util.Constants; +import net.minecraftforge.common.util.Constants.NBT; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; import com.cardinalstar.cubicchunks.api.IHeightMap; import com.cardinalstar.cubicchunks.lighting.ILightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; -import com.cardinalstar.cubicchunks.util.AddressTools; +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.falsepattern.chunk.internal.DataRegistryImpl; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; @ParametersAreNonnullByDefault public class IONbtReader { @@ -60,12 +65,18 @@ static Chunk readColumn(World world, int x, int z, NBTTagCompound nbt) { if (column == null) { return null; } - readBiomes(level, column); - readOpacityIndex(level, column); + + if (!Mods.ChunkAPI.isModLoaded()) { + column.inhabitedTime = level.getLong("InhabitedTime"); + readBiomes(level, column); + readOpacityIndex(level, column); + } else { + DataRegistryImpl.readChunkFromNBT(column, nbt); + } column.isModified = false; // its exactly the same as on disk so its not modified ((IColumnInternal) column).setColumn(true); - return column; // TODO: use Chunk, not IColumn, whenever possible + return column; } @Nullable @@ -89,25 +100,16 @@ private static Chunk readBaseColumn(World world, int x, int z, NBTTagCompound nb return null; } - // create the column - Chunk column = new Chunk(world, x, z); - - // read the rest of the column properties - column.inhabitedTime = nbt.getLong("InhabitedTime"); - - // if (column.getCapabilities() != null && nbt.hasKey("ForgeCaps")) { - // column.getCapabilities().deserializeNBT(nbt.getCompoundTag("ForgeCaps")); - // } - return column; + return new Chunk(world, x, z); } private static void readBiomes(NBTTagCompound nbt, Chunk column) {// biomes System.arraycopy(nbt.getByteArray("Biomes"), 0, column.getBiomeArray(), 0, Cube.SIZE * Cube.SIZE); } - private static void readOpacityIndex(NBTTagCompound nbt, Chunk chunk) {// biomes + private static void readOpacityIndex(NBTTagCompound nbt, Chunk chunk) { IHeightMap hmap = ((IColumn) chunk).getOpacityIndex(); - ((ServerHeightMap) hmap).readData(nbt.getByteArray("OpacityIndex")); + ((ServerHeightMap) hmap).readData(new CCPacketBuffer(Unpooled.wrappedBuffer(nbt.getByteArray("OpacityIndex")))); } static CubeInitLevel getCubeInitLevel(NBTTagCompound nbt) { @@ -176,12 +178,29 @@ static Cube readCube(Chunk column, final int cubeX, final int cubeY, final int c // this status will get unset again in readLightingInfo() if the lighting engine is changed (LightingInfoType). cube.setInitialLightingDone(level.getBoolean("initLightDone")); - // if (cube.getCapabilities() != null && nbt.hasKey("ForgeCaps")) { - // cube.getCapabilities().deserializeNBT(nbt.getCompoundTag("ForgeCaps")); - // } + NBTTagList sections = level.getTagList("Sections", NBT.TAG_COMPOUND); + + // Block loading has to be done before TE loading, otherwise the TEs get deleted + if (sections.tagCount() > 0) { + NBTTagCompound section = sections.getCompoundTagAt(0); + + if (Mods.ChunkAPI.isModLoaded()) { + section.setInteger("Y", cubeY); + + ExtendedBlockStorage ebs = new ExtendedBlockStorage( + Coords.cubeToMinBlock(cube.getY()), + !cube.getWorld().provider.hasNoSky); + + DataRegistryImpl.readSubChunkFromNBT(cube.getColumn(), ebs, section); + + ebs.removeInvalidBlocks(); + cube.setStorageFromSave(ebs); + } else { + readBlocks(section, world, cube); + } + } readBiomes(cube, level); - readBlocks(level, world, cube); readEntities(level, world, cube); readTileEntities(level, world, cube); readScheduledBlockTicks(level, world); @@ -193,32 +212,26 @@ static Cube readCube(Chunk column, final int cubeX, final int cubeY, final int c return cube; } - private static void readBlocks(NBTTagCompound nbt, World world, Cube cube) { - boolean isEmpty = !nbt.hasKey("Sections");// is this an empty cube? - if (!isEmpty) { - NBTTagList sectionList = nbt.getTagList("Sections", 10); - nbt = sectionList.getCompoundTagAt(0); + private static void readBlocks(NBTTagCompound section, World world, Cube cube) { + ExtendedBlockStorage ebs = new ExtendedBlockStorage( + Coords.cubeToMinBlock(cube.getY()), + !cube.getWorld().provider.hasNoSky); - ExtendedBlockStorage ebs = new ExtendedBlockStorage( - Coords.cubeToMinBlock(cube.getY()), - !cube.getWorld().provider.hasNoSky); - - ebs.setBlockLSBArray(nbt.getByteArray("BlocksLSB")); - if (nbt.hasKey("BlocksMSB")) { - ebs.setBlockMSBArray(new NibbleArray(nbt.getByteArray("BlocksMSB"), 4)); - } + ebs.setBlockLSBArray(section.getByteArray("Blocks")); + if (section.hasKey("Add")) { + ebs.setBlockMSBArray(new NibbleArray(section.getByteArray("Add"), 4)); + } - ebs.setBlockMetadataArray(new NibbleArray(nbt.getByteArray("BlocksMeta"), 4)); + ebs.setBlockMetadataArray(new NibbleArray(section.getByteArray("Data"), 4)); - ebs.setBlocklightArray(new NibbleArray(nbt.getByteArray("BlockLight"), 4)); + ebs.setBlocklightArray(new NibbleArray(section.getByteArray("BlockLight"), 4)); - if (!world.provider.hasNoSky) { - ebs.setSkylightArray(new NibbleArray(nbt.getByteArray("SkyLight"), 4)); - } - - ebs.removeInvalidBlocks(); - cube.setStorageFromSave(ebs); + if (!world.provider.hasNoSky) { + ebs.setSkylightArray(new NibbleArray(section.getByteArray("SkyLight"), 4)); } + + ebs.removeInvalidBlocks(); + cube.setStorageFromSave(ebs); } private static void readEntities(NBTTagCompound cubeNbt, World world, Cube cube) {// entities @@ -349,31 +362,10 @@ private static void readLightingInfo(Cube cube, NBTTagCompound nbt, World world) } private static void readBiomes(Cube cube, NBTTagCompound nbt) {// biomes - if (nbt.hasKey("Biomes3D")) { - cube.setBiomeArray(nbt.getByteArray("Biomes3D")); - } if (nbt.hasKey("Biomes")) { - cube.setBiomeArray(convertFromOldCubeBiomes(nbt.getByteArray("Biomes"))); - } - } + ByteBuf data = Unpooled.wrappedBuffer(nbt.getByteArray("Biomes")); - private static byte[] convertFromOldCubeBiomes(byte[] biomes) { - byte[] newBiomes = new byte[4 * 4 * 4]; - for (int x = 0; x < 4; x++) { - for (int y = 0; y < 4; y++) { - for (int z = 0; z < 4; z++) { - // NOTE: spread the biomes from 4 2x2 segments into the 4 vertical 4x4x4 segments - // this ensures that no biome data has been lost, but some of it may get arranged weirdly - newBiomes[AddressTools.getBiomeAddress3d(x, y, z)] = biomes[getOldBiomeAddress( - x << 1 | (y & 1), - z << 1 | ((y >> 1) & 1))]; - } - } + cube.readBiomeArray(new CCPacketBuffer(data)); } - return newBiomes; - } - - public static int getOldBiomeAddress(int biomeX, int biomeZ) { - return biomeX << 3 | biomeZ; } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java index c17b0077..7bafae22 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java @@ -45,11 +45,16 @@ import com.cardinalstar.cubicchunks.api.IHeightMap; import com.cardinalstar.cubicchunks.lighting.ILightingManager; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; import com.cardinalstar.cubicchunks.util.Coords; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.falsepattern.chunk.internal.DataRegistryImpl; import cpw.mods.fml.common.FMLLog; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @ParametersAreNonnullByDefault @@ -57,14 +62,21 @@ class IONbtWriter { static NBTTagCompound write(Chunk column) { NBTTagCompound columnNbt = new NBTTagCompound(); + NBTTagCompound level = new NBTTagCompound(); columnNbt.setTag("Level", level); - // columnNbt.setInteger("DataVersion", FMLCommonHandler.instance().getDataFixer().version); - // FMLCommonHandler.instance().getDataFixer().writeVersionData(columnNbt); + writeBaseColumn(column, level); - writeBiomes(column, level); writeOpacityIndex(column, level); + + if (Mods.ChunkAPI.isModLoaded()) { + DataRegistryImpl.writeChunkToNBT(column, columnNbt); + } else { + writeBiomes(column, level); + } + EVENT_BUS.post(new ChunkDataEvent.Save(column, columnNbt)); + return columnNbt; } @@ -73,15 +85,28 @@ static NBTTagCompound write(final Cube cube) { // Added to preserve compatibility with vanilla NBT chunk format. NBTTagCompound level = new NBTTagCompound(); cubeNbt.setTag("Level", level); - // cubeNbt.setInteger("DataVersion", FMLCommonHandler.instance().getDataFixer().version); - // FMLCommonHandler.instance().getDataFixer().writeVersionData(cubeNbt); writeBaseCube(cube, level); - writeBlocks(cube, level); + + if (cube.getStorage() != null) { + NBTTagList sections = new NBTTagList(); + level.setTag("Sections", sections); + + NBTTagCompound section = new NBTTagCompound(); + sections.appendTag(section); + + if (!Mods.ChunkAPI.isModLoaded()) { + writeBlocks(cube, section); + } else { + DataRegistryImpl.writeSubChunkToNBT(cube.getColumn(), cube.getStorage(), section); + } + } + writeEntities(cube, level); writeTileEntities(cube, level); writeScheduledTicks(cube, level); writeLightingInfo(cube, level); writeBiomes(cube, level); + return cubeNbt; } @@ -92,16 +117,6 @@ private static void writeBaseColumn(Chunk column, NBTTagCompound nbt) {// coords // column properties nbt.setByte("v", (byte) 1); nbt.setLong("InhabitedTime", column.inhabitedTime); - - // if (column.getCapabilities() != null) { - // try { - // nbt.setTag("ForgeCaps", column.getCapabilities().serializeNBT()); - // } catch (Exception exception) { - // CubicChunks.LOGGER.error("A capability provider has thrown an exception trying to write state. It will not - // persist. " - // + "Report this to the mod author", exception); - // } - // } } private static void writeBiomes(Chunk column, NBTTagCompound nbt) {// biomes @@ -130,34 +145,18 @@ private static void writeBaseCube(Cube cube, NBTTagCompound cubeNbt) { cubeNbt.setBoolean("isSurfaceTracked", cube.isSurfaceTracked()); cubeNbt.setBoolean("initLightDone", cube.isInitialLightingDone()); - - // if (cube.getCapabilities() != null) { - // try { - // cubeNbt.setTag("ForgeCaps", cube.getCapabilities().serializeNBT()); - // } catch (Exception exception) { - // CubicChunks.LOGGER.error("A capability provider has thrown an exception trying to write state. It will not - // persist. " - // + "Report this to the mod author", exception); - // } - // } } - private static void writeBlocks(Cube cube, NBTTagCompound cubeNbt) { + private static void writeBlocks(Cube cube, NBTTagCompound section) { ExtendedBlockStorage ebs = cube.getStorage(); - if (ebs == null) { - return; // no data to save anyway - } - NBTTagList sectionList = new NBTTagList(); - NBTTagCompound section = new NBTTagCompound(); - sectionList.appendTag(section); - cubeNbt.setTag("Sections", sectionList); - - ExtendedBlockStorage storage = cube.getStorage(); - section.setByteArray("BlocksLSB", storage.getBlockLSBArray()); - if (storage.getBlockMSBArray() != null) { - section.setByteArray("BlocksMSB", storage.getBlockMSBArray().data); + assert ebs != null; + + section.setByteArray("Blocks", ebs.getBlockLSBArray()); + + if (ebs.getBlockMSBArray() != null) { + section.setByteArray("Add", ebs.getBlockMSBArray().data); } - section.setByteArray("BlocksMeta", storage.getMetadataArray().data); + section.setByteArray("Data", ebs.getMetadataArray().data); section.setByteArray("BlockLight", ebs.getBlocklightArray().data); @@ -248,9 +247,18 @@ private static void writeLightingInfo(Cube cube, NBTTagCompound cubeNbt) { lightingManager.writeToNbt(cube, lightingInfo); } - private static void writeBiomes(Cube cube, NBTTagCompound nbt) {// biomes - byte[] biomes = cube.getBiomeArray(); - if (biomes != null) nbt.setByteArray("Biomes3D", biomes); + private static void writeBiomes(Cube cube, NBTTagCompound nbt) { + ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); + try { + cube.writeBiomeArray(new CCPacketBuffer(buffer)); + + byte[] data = new byte[buffer.writerIndex()]; + buffer.readBytes(data); + + nbt.setByteArray("Biomes", data); + } finally { + buffer.release(); + } } private static List getScheduledTicks(Cube cube) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/AddressTools.java b/src/main/java/com/cardinalstar/cubicchunks/util/AddressTools.java index 8a43de71..86e269a7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/AddressTools.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/AddressTools.java @@ -64,8 +64,4 @@ public static int getLocalY(int localAddress) { public static int getLocalZ(int localAddress) { return Bits.unpackUnsigned(localAddress, 4, 4); } - - public static int getBiomeAddress3d(int biomeLocalX, int biomeLocalY, int biomeLocalZ) { - return biomeLocalX | biomeLocalY << 2 | biomeLocalZ << 4; - } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java index eed1442d..06b4e0c6 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -1,9 +1,22 @@ package com.cardinalstar.cubicchunks.util; +import java.awt.*; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +import org.joml.primitives.AABBd; + +import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; +// import com.gtnewhorizon.gtnhlib.visualization.BoxVisualizer; +// import com.gtnewhorizon.gtnhlib.visualization.VisualizedBox; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent; @@ -22,43 +35,71 @@ public enum CubeStatus { private static final ConcurrentHashMap cubeStatus = new ConcurrentHashMap<>(); private static final AtomicBoolean dirty = new AtomicBoolean(); + private static boolean wasSent = false; + + public static void reset() { + cubeStatus.clear(); + dirty.set(false); + } + + @SubscribeEvent public static void sync(ServerTickEvent event) { if (event.phase != Phase.END) return; if (!dirty.compareAndSet(true, false)) return; -// List boxes = new ArrayList<>(); -// -// cubeStatus.forEach((pos, status) -> { -// boxes.add(new VisualizedBox( -// switch (status) { -// case None -> new Color(100, 50, 100); -// case Generated -> new Color(50, 200, 50); -// case Populated -> new Color(50, 50, 200); -// case Lit -> new Color(200, 200, 50); -// case Synced -> new Color(200, 50, 50); -// }, -// new AABBf( -// pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), -// pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ()) -// )); -// }); -// -// for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { -// BoxVisualizer.sendBoxes(player, Duration.ofMinutes(5), boxes, true); -// } + if (!CubicChunksConfig.enableChunkStatusDebugging) { + if (wasSent) { + wasSent = false; + for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + // BoxVisualizer.sendBoxes(player, Duration.ofMinutes(0), new ArrayList<>(), true); + } + } + + return; + } + + List boxes = new ArrayList<>(); + + cubeStatus.forEach((pos, status) -> { + // boxes.add(new VisualizedBox( + // switch (status) { + // case None -> new Color(100, 50, 100, 50); + // case Generated -> new Color(50, 200, 50, 50); + // case Populated -> new Color(50, 50, 200, 50); + // case Lit -> new Color(200, 200, 50, 50); + // case Dirty -> new Color(14, 229, 187, 50); + // case Synced -> new Color(200, 50, 50, 50); + // }, + // new AABBd( + // pos.getMinBlockX() - 0.5, pos.getMinBlockY() + 0.5, pos.getMinBlockZ() - 0.5, + // pos.getMaxBlockX() - 0.5, pos.getMaxBlockY() - 0.5, pos.getMaxBlockZ() - 0.5) + // )); + }); + + wasSent = true; + + for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + // BoxVisualizer.sendBoxes(player, Duration.ofMinutes(5), boxes, true); + } } public static void put(CubePos pos, CubeStatus status) { + if (pos.getY() != 4) return; + cubeStatus.put(pos, status); dirty.set(true); } public static void cmpexc(CubePos pos, CubeStatus expected, CubeStatus desired) { + if (pos.getY() != 4) return; + cubeStatus.compute(pos, (key, existing) -> existing == expected ? desired : existing); dirty.set(true); } public static void remove(CubePos pos) { + if (pos.getY() != 4) return; + cubeStatus.remove(pos); dirty.set(true); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DirectionUtils.java b/src/main/java/com/cardinalstar/cubicchunks/util/DirectionUtils.java index 0f3f7b2d..4c8f75f4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DirectionUtils.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DirectionUtils.java @@ -4,7 +4,7 @@ import org.joml.Vector3i; -import com.gtnewhorizon.gtnhlib.client.renderer.quad.Axis; +import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.properties.ModelQuadFacing.Axis; public class DirectionUtils { diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/Mods.java b/src/main/java/com/cardinalstar/cubicchunks/util/Mods.java index 0e167188..fbb27410 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/Mods.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Mods.java @@ -62,6 +62,7 @@ public enum Mods implements IMod, ITargetMod { CatWalks(ModIDs.CAT_WALKS), Chisel(ModIDs.CHISEL), ChiselTones(ModIDs.CHISEL_TONES), + ChunkAPI(ModIDs.CHUNK_API, "com.falsepattern.chunk.internal.core.CoreLoadingPlugin"), CodeChickenCore(ModIDs.CODE_CHICKEN_CORE), Computronics(ModIDs.COMPUTRONICS), Controlling(ModIDs.CONTROLLING), @@ -85,6 +86,7 @@ public enum Mods implements IMod, ITargetMod { EnderIO(ModIDs.ENDER_I_O), EnderStorage(ModIDs.ENDER_STORAGE), EnderZoo(ModIDs.ENDER_ZOO), + EndlessIDs(ModIDs.ENDLESS_IDS, "com.falsepattern.endlessids.asm.EndlessIDsCore"), EnhancedLootBags(ModIDs.ENHANCED_LOOT_BAGS), EtFuturumRequiem(ModIDs.ET_FUTURUM_REQUIEM), EternalSingularity(ModIDs.ETERNAL_SINGULARITY), @@ -159,7 +161,7 @@ protected String getEffectiveModID() { NewHorizonsCoreMod(ModIDs.NEW_HORIZONS_CORE_MOD), NodalMechanics(ModIDs.NODAL_MECHANICS), NotEnoughEnergistics(ModIDs.NOT_ENOUGH_ENERGISTICS), - NotEnoughIDs(ModIDs.NOT_ENOUGH_I_DS), + NotEnoughIDs(ModIDs.NOT_ENOUGH_I_DS, "com.gtnewhorizons.neid.core.NEIDCore"), NotEnoughItems(ModIDs.NOT_ENOUGH_ITEMS), IC2NuclearControl(ModIDs.I_C2_NUCLEAR_CONTROL), Nutrition(ModIDs.NUTRITION), @@ -358,6 +360,7 @@ public static class ModIDs { public static final String CHISEL = "chisel", CHISEL_API = "ChiselAPI"; public static final String CHISEL_TONES = "chiseltones"; + public static final String CHUNK_API = "chunkapi"; public static final String CODE_CHICKEN_CORE = "CodeChickenCore"; public static final String COMPUTRONICS = "computronics"; public static final String CONTROLLING = "controlling"; @@ -381,6 +384,7 @@ public static class ModIDs { public static final String ENDER_I_O = "EnderIO"; public static final String ENDER_STORAGE = "EnderStorage"; public static final String ENDER_ZOO = "EnderZoo"; + public static final String ENDLESS_IDS = "endlessids"; public static final String ENHANCED_LOOT_BAGS = "enhancedlootbags"; public static final String ET_FUTURUM_REQUIEM = "etfuturum"; public static final String ETERNAL_SINGULARITY = "eternalsingularity"; diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java new file mode 100644 index 00000000..489d385f --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java @@ -0,0 +1,22 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import net.minecraft.world.biome.BiomeGenBase; + +import org.jetbrains.annotations.Nullable; + +import com.cardinalstar.cubicchunks.util.AddressTools; +import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; + +public interface BiomeArray extends Int2ObjectFunction { + + boolean isEmpty(); + + default BiomeGenBase put(int x, int y, int z, BiomeGenBase value) { + return put(AddressTools.getLocalAddress(x, y, z), value); + } + + @Nullable + default BiomeGenBase get(int x, int y, int z) { + return get(AddressTools.getLocalAddress(x, y, z)); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java new file mode 100644 index 00000000..6b3594e0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java @@ -0,0 +1,145 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import net.minecraft.world.biome.BiomeGenBase; + +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +public class DynamicBiomeArray implements BiomeArray { + + private BiomeArray backing = new PalettizedBiomeArray(); + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public int size() { + return 16 * 16 * 16; + } + + @Override + public void clear() { + backing.clear(); + } + + @Override + public BiomeGenBase put(int key, BiomeGenBase value) { + try { + backing.put(key, value); + } catch (PaletteFullError e) { + BiomeArray reference = new ReferenceBiomeArray(); + + int size = size(); + + for (int i = 0; i < size; i++) { + reference.put(i, backing.get(i)); + } + + backing = reference; + + backing.put(key, value); + } + + return null; + } + + @Override + public BiomeGenBase get(int key) { + return backing.get(key); + } + + @Override + public void defaultReturnValue(BiomeGenBase rv) { + backing.defaultReturnValue(rv); + } + + @Override + public BiomeGenBase defaultReturnValue() { + return backing.defaultReturnValue(); + } + + private static final byte ADD_PALLETE = 0; + private static final byte DATA = 1; + + public void write(CCPacketBuffer buffer) { + Object2IntOpenHashMap palette = new Object2IntOpenHashMap<>(); + + palette.defaultReturnValue(-1); + + int start = buffer.writerIndex(); + + int ops = 0; + + for (int i = 0; i < size(); i++) { + BiomeGenBase curr = get(i); + + int paletteIndex = palette.getInt(curr); + + if (paletteIndex == -1) { + paletteIndex = palette.size(); + palette.put(curr, paletteIndex); + + buffer.writeByte(ADD_PALLETE); + buffer.writeVarIntToBuffer(curr.biomeID); + ops++; + } + + int i2 = i + 1; + + while (i2 < size() && get(i2) == curr) { + i2++; + } + + int len = i2 - i; + + buffer.writeByte(DATA); + buffer.writeVarIntToBuffer(paletteIndex); + buffer.writeVarIntToBuffer(len); + ops++; + } + + int end = buffer.writerIndex(); + + buffer.writerIndex(start); + buffer.writeInt(ops); + + buffer.writerIndex(end); + } + + public void read(CCPacketBuffer buffer) { + int ops = buffer.readInt(); + + ObjectArrayList palette = new ObjectArrayList<>(); + + int i = 0; + + for (int op = 0; op < ops; op++) { + byte insn = buffer.readByte(); + switch (insn) { + case ADD_PALLETE -> { + BiomeGenBase biome = BiomeGenBase.getBiome(buffer.readVarIntFromBuffer()); + + palette.add(biome); + } + case DATA -> { + BiomeGenBase biome = palette.get(buffer.readVarIntFromBuffer()); + int len = buffer.readVarIntFromBuffer(); + + for (int k = 0; k < len; k++) { + put(i + k, biome); + } + + i += len; + } + default -> { + throw new InvalidBiomeDataException("Unexpected operation " + insn + " at index" + buffer.readerIndex()); + } + } + } + + if (i != size()) throw new InvalidBiomeDataException("Expected " + size() + " biomes indices, got " + i); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java new file mode 100644 index 00000000..467ab099 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +public class InvalidBiomeDataException extends RuntimeException { + + public InvalidBiomeDataException(String message) { + super(message); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java new file mode 100644 index 00000000..3a94d564 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java @@ -0,0 +1,8 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +public class PaletteFullError extends RuntimeException { + + public PaletteFullError() { + super(null, null, false, false); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java new file mode 100644 index 00000000..429bbd6e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java @@ -0,0 +1,131 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import java.util.Arrays; + +import net.minecraft.world.biome.BiomeGenBase; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +public class PalettizedBiomeArray implements BiomeArray { + + private int freePaletteIndices = ~0b1; + private final BiomeGenBase[] palette = new BiomeGenBase[16]; + private final Object2IntOpenHashMap paletteReversed = new Object2IntOpenHashMap<>(); + + { + paletteReversed.defaultReturnValue(-1); + paletteReversed.put(null, 0); + palette[0] = null; + } + + private final byte[] data = new byte[16 * 16 * 16 / 2]; + + @Override + public boolean isEmpty() { + for (byte b : data) { + if (b != 0) return false; + } + + return true; + } + + @Override + public int size() { + return 16 * 16 * 16; + } + + @Override + public void clear() { + Arrays.fill(data, (byte) 0); + } + + @Override + public BiomeGenBase put(int key, BiomeGenBase value) { + int paletteIndex = paletteReversed.getInt(value); + + if (paletteIndex == -1) { + int free = Integer.lowestOneBit(freePaletteIndices); + + if (free >= 16) { + cleanPalette(); + + free = Integer.lowestOneBit(freePaletteIndices); + + if (free >= 16) { + throw new PaletteFullError(); + } + } + + paletteIndex = free; + palette[free] = value; + paletteReversed.put(value, free); + freePaletteIndices &= ~(0b1 << paletteIndex); + } + + byte b = data[key >> 1]; + + if ((key & 0b1) == 0) { + b = (byte) ((b & 0xF0) | paletteIndex); + } else { + b = (byte) ((paletteIndex << 4) | (b & 0xF)); + } + + data[key >> 1] = b; + + return null; + } + + @Override + public BiomeGenBase get(int key) { + byte b = data[key >> 1]; + + if ((key & 0b1) == 0) { + b >>= 4; + } + + b &= 0xF; + + return palette[b]; + } + + @Override + public void defaultReturnValue(BiomeGenBase rv) { + paletteReversed.removeInt(defaultReturnValue()); + + palette[0] = rv; + + paletteReversed.put(rv, 0); + } + + @Override + public BiomeGenBase defaultReturnValue() { + return palette[0]; + } + + public void cleanPalette() { + freePaletteIndices = ~0b1; + + for (byte b : data) { + int lower = b & 0xF; + int upper = (b >> 4) & 0xF; + + freePaletteIndices &= ~(0b1 << lower); + freePaletteIndices &= ~(0b1 << upper); + } + + paletteReversed.clear(); + + // default biome + paletteReversed.put(palette[0], 0); + + for (int i = 1; i < 16; i++) { + if ((freePaletteIndices & (0b1 << i)) != 0) { + palette[i] = null; + } else { + if (palette[i] != null) { + paletteReversed.put(palette[i], i); + } + } + } + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/ReferenceBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/ReferenceBiomeArray.java new file mode 100644 index 00000000..e5e9b186 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/ReferenceBiomeArray.java @@ -0,0 +1,55 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import java.util.Arrays; + +import net.minecraft.world.biome.BiomeGenBase; + +public class ReferenceBiomeArray implements BiomeArray { + + private BiomeGenBase def = null; + + private final BiomeGenBase[] data = new BiomeGenBase[16 * 16 * 16]; + + @Override + public boolean isEmpty() { + for (BiomeGenBase b : data) { + if (b != null) return false; + } + + return true; + } + + @Override + public int size() { + return 16 * 16 * 16; + } + + @Override + public void clear() { + Arrays.fill(data, null); + } + + @Override + public BiomeGenBase put(int key, BiomeGenBase value) { + data[key] = value; + + return null; + } + + @Override + public BiomeGenBase get(int key) { + BiomeGenBase biome = data[key]; + + return biome == null ? def : biome; + } + + @Override + public void defaultReturnValue(BiomeGenBase rv) { + def = rv; + } + + @Override + public BiomeGenBase defaultReturnValue() { + return def; + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/api/ICubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/world/api/ICubeProviderServer.java index 2a34356a..1303877e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/api/ICubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/api/ICubeProviderServer.java @@ -69,7 +69,7 @@ enum Requirement { */ GET_CACHED, /** - * Load the NBT from disk, but don't load the chunk into the world at all + * Load the NBT from disk, but don't load the cube/column into the world at all */ NBT, /** diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java b/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java index 41f2c818..d06c8d23 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java @@ -21,8 +21,6 @@ package com.cardinalstar.cubicchunks.world.core; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.IOException; import javax.annotation.Nonnull; @@ -30,8 +28,11 @@ import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IHeightMap; +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.world.cube.Cube; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; @ParametersAreNonnullByDefault public class ServerHeightMap implements IHeightMap { @@ -716,64 +717,52 @@ private static int getIndex(int localX, int localZ) { // Serialization / NBT --------------------------------------------------------------------------------------------- public byte[] getData() { + ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(); + try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - writeData(out); - out.close(); - return buf.toByteArray(); + writeData(new CCPacketBuffer(buf)); + + byte[] data = new byte[buf.writerIndex()]; + buf.readBytes(data); + return data; } catch (IOException ex) { throw new Error(ex); + } finally { + buf.release(); } } - public void readData(byte[] data) { - int pos = 0; + public void readData(CCPacketBuffer buffer) { for (int i = 0; i < this.segments.length; i++) { - this.ymin[i] = readIntBigEndian(data, pos); - pos += Integer.BYTES; - this.ymax.set(i, readIntBigEndian(data, pos)); - pos += Integer.BYTES; - int[] segments = new int[readUShortBigEndian(data, pos)]; - pos += Short.BYTES; - if (segments.length == 0) { - continue; - } + this.ymin[i] = buffer.readVarIntFromBuffer(); + this.ymax.set(i, buffer.readVarIntFromBuffer()); + + int[] segments = new int[buffer.readVarIntFromBuffer()]; + if (segments.length == 0) continue; + for (int j = 0; j < segments.length; j++) { - segments[j] = readIntBigEndian(data, pos); - pos += Integer.BYTES; + segments[j] = buffer.readVarIntFromBuffer(); } + this.segments[i] = segments; + assert parityCheck(i) : "The number of segments was wrong!"; } } - private int readIntBigEndian(byte[] arr, int pos) { - int ch1 = arr[pos] & 0xFF; - int ch2 = arr[pos + 1] & 0xFF; - int ch3 = arr[pos + 2] & 0xFF; - int ch4 = arr[pos + 3] & 0xFF; - return (ch1 << 24) | (ch2 << 16) | (ch3 << 8) | ch4; - } - - private int readUShortBigEndian(byte[] arr, int pos) { - int ch1 = arr[pos] & 0xFF; - int ch2 = arr[pos + 1] & 0xFF; - return (ch1 << 8) | ch2; - } - - private void writeData(DataOutputStream out) throws IOException { + private void writeData(CCPacketBuffer buffer) throws IOException { for (int i = 0; i < this.segments.length; i++) { - out.writeInt(this.ymin[i]); - out.writeInt(this.ymax.get(i)); + buffer.writeVarIntToBuffer(this.ymin[i]); + buffer.writeVarIntToBuffer(this.ymax.get(i)); + int[] segments = this.segments[i]; if (segments == null || segments.length == 0) { - out.writeShort(0); + buffer.writeVarIntToBuffer(0); } else { int lastSegmentIndex = getLastSegmentIndex(segments); - out.writeShort(lastSegmentIndex + 1); + buffer.writeVarIntToBuffer(lastSegmentIndex + 1); for (int j = 0; j <= lastSegmentIndex; j++) { - out.writeInt(segments[j]); + buffer.writeVarIntToBuffer(segments[j]); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java index ed232b13..fe33710f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -58,6 +58,7 @@ import net.minecraftforge.event.world.ChunkEvent; import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.NotNull; import com.cardinalstar.cubicchunks.CubicChunks; import com.cardinalstar.cubicchunks.api.IColumn; @@ -66,12 +67,14 @@ import com.cardinalstar.cubicchunks.api.MetaKey; import com.cardinalstar.cubicchunks.event.events.CubeEvent; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldInternal; +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; import com.cardinalstar.cubicchunks.server.SpawnCubes; import com.cardinalstar.cubicchunks.util.AddressTools; import com.cardinalstar.cubicchunks.util.CompatHandler; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.TicketList; +import com.cardinalstar.cubicchunks.util.biome3d.DynamicBiomeArray; import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.core.ICubicTicketInternal; @@ -93,7 +96,7 @@ public class Cube implements ICube { * A 16x16x16 mapping of the block biome array. */ @Nullable - private byte[] blockBiomeArray = null; + private DynamicBiomeArray biomes3d = null; @Nonnull private final TicketList tickets; // tickets prevent this Cube from being unloaded @@ -476,40 +479,42 @@ public void tickCubeServer(BooleanSupplier tryToTickFaster, Random rand) { tickCubeCommon(tryToTickFaster); } - /** - * @return biome or null if {@link #blockBiomeArray} is not generated by - * cube generator. Input coordinates are in cube coordinate space. - */ @Override - public BiomeGenBase getBiome(int biomeX, int biomeY, int biomeZ) { - if (this.blockBiomeArray == null) return this.getColumn() - .getBiomeGenForWorldCoords(biomeX, biomeY, world.provider.worldChunkMgr); // TODO - int biomeId = this.blockBiomeArray[AddressTools.getBiomeAddress3d(biomeX, biomeY, biomeZ)] & 255; - return BiomeGenBase.getBiome(biomeId); + public @NotNull BiomeGenBase getBiome(int biomeX, int biomeY, int biomeZ) { + BiomeGenBase biome = biomes3d == null ? null : biomes3d.get(biomeX, biomeY, biomeZ); + + if (biome == null) { + biome = this.getColumn().getBiomeGenForWorldCoords(biomeX, biomeY, world.provider.worldChunkMgr); + } + + return biome; } @Override - public void setBiome(int localBiomeX, int localBiomeY, int localBiomeZ, BiomeGenBase biome) { - if (this.blockBiomeArray == null) this.blockBiomeArray = new byte[16 * 16 * 16]; + public void setBiome(int biomeX, int biomeY, int biomeZ, BiomeGenBase biome) { + if (biomes3d == null) { + biomes3d = new DynamicBiomeArray(); + } - this.blockBiomeArray[AddressTools - .getBiomeAddress3d(localBiomeX, localBiomeY, localBiomeZ)] = (byte) biome.biomeID; + biomes3d.put(biomeX, biomeY, biomeZ, biome); } - @Nullable - public byte[] getBiomeArray() { - return this.blockBiomeArray; + public void writeBiomeArray(CCPacketBuffer buffer) { + if (biomes3d == null || biomes3d.isEmpty()) { + buffer.writeBoolean(false); + } else { + buffer.writeBoolean(true); + biomes3d.write(buffer); + } } - public void setBiomeArray(byte[] biomeArray) { - if (this.blockBiomeArray == null) this.blockBiomeArray = biomeArray; - if (this.blockBiomeArray.length != biomeArray.length) { - CubicChunks.LOGGER.warn( - "Could not set level cube biomes, array length is {} instead of {}", - Integer.valueOf(biomeArray.length), - Integer.valueOf(this.blockBiomeArray.length)); - } else { - System.arraycopy(biomeArray, 0, this.blockBiomeArray, 0, this.blockBiomeArray.length); + public void readBiomeArray(CCPacketBuffer buffer) { + biomes3d = null; + + if (buffer.readBoolean()) { + biomes3d = new DynamicBiomeArray(); + + biomes3d.read(buffer); } } // ================================= @@ -667,6 +672,15 @@ public void onCubeLoad() { .onCubeLoad(this); CompatHandler.onCubeLoad(new ChunkEvent.Load(getColumn())); EVENT_BUS.post(new CubeEvent.Load(world, coords, this)); + + ICubicWorldInternal world = this.getWorld(); + + int min = this.getY() << 4; + int max = (this.getY() + 1) << 4; + + if (min < world.getMinHeight() || max > world.getMaxHeight()) { + world.setHeightBounds(Math.min(min, world.getMinHeight()), Math.max(max, world.getMaxHeight())); + } } @SuppressWarnings("deprecation") diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/NoodleCaveGenerator.java similarity index 100% rename from src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/NoodleCaveGenerator.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/NoodleCaveGenerator.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/SpaghettiCaveGenerator.java similarity index 100% rename from src/main/java/com/cardinalstar/cubicchunks/world/worldgen/modern/SpaghettiCaveGenerator.java rename to src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/SpaghettiCaveGenerator.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index 69dff2cb..49a9a7c9 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -21,13 +21,13 @@ package com.cardinalstar.cubicchunks.worldgen; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Random; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.block.Block; @@ -253,34 +253,39 @@ public GenerationResult provideColumn(World world, int columnX, int colum return new GenerationResult<>(data.left(), null, cubes); } + @Override + public void recreateStructures(ICube cube) { + + } + @Override public void recreateStructures(Chunk column) { - vanilla.recreateStructures(column.xPosition, column.zPosition); // TODO WATCH + vanilla.recreateStructures(column.xPosition, column.zPosition); } @Override - public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int cubeZ) { + public GenerationResult provideCube(@Nullable Chunk chunk, int cubeX, int cubeY, int cubeZ) { try { WorldgenHangWatchdog.startWorldGen(); - IBlockView cubeData; - - if (cubeY < 0) { - FillerInfo fillerInfo = getBottomFillerInfo(); + List generatedColumns = new ArrayList<>(); + List generatedCubes = new ArrayList<>(); - cubeData = new UniformBlockView(fillerInfo.filler); - } else if (cubeY >= worldHeightCubes) { - FillerInfo fillerInfo = getTopFillerInfo(); - - cubeData = new UniformBlockView(fillerInfo.filler); - } else { + if (cubeY >= 0 && cubeY < 16 || chunk == null) { + // Generate the vanilla chunk Pair data = getVanillaChunkView(cubeX, cubeZ); - Cube self = null; - List sideEffects = new ArrayList<>(); + IBlockView chunkBlocks = data.right(); + + if (chunk != null) { + CubicChunks.LOGGER.error("Needed to regenerate a cube within the vanilla chunk for a chunk that already exists: something is fucky ({},{},{})", cubeX, cubeY, cubeZ, new Exception()); + } else { + chunk = data.left(); + generatedColumns.add(chunk); + } // Ceiling div by 16 - int heightCubes = (data.right() + int heightCubes = (chunkBlocks .getBounds() .getSizeY() + 15) >> 4; @@ -288,7 +293,7 @@ public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int Cube c = new Cube( chunk, y, - data.right() + chunkBlocks .subView(Box.horizontalChunkSlice(y << 4, 16))); try { @@ -297,25 +302,38 @@ public GenerationResult provideCube(Chunk chunk, int cubeX, int cubeY, int CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, y, cubeZ, t); } - if (y == cubeY) { - self = c; - } else { - sideEffects.add(c); - } + generatedCubes.add(c); + } + } + + if (cubeY < 0 || cubeY >= 16) { + FillerInfo fillerInfo = cubeY < 0 ? getBottomFillerInfo() : getTopFillerInfo(); + IBlockView cubeData = new UniformBlockView(fillerInfo.filler); + + Cube cube = new Cube(chunk, cubeY, cubeData); + + try { + decorator.generate(world, cube); + } catch (Throwable t) { + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); } - return new GenerationResult<>(self, Collections.singletonList(data.left()), sideEffects); + generatedCubes.add(cube); } - Cube cube = new Cube(chunk, cubeY, cubeData); + Cube primary = null; - try { - decorator.generate(world, cube); - } catch (Throwable t) { - CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); + for (int i = 0; i < generatedCubes.size(); i++) { + Cube c = generatedCubes.get(i); + + if (c.getY() == cubeY) { + primary = c; + generatedCubes.remove(i); + break; + } } - return new GenerationResult<>(cube); + return new GenerationResult<>(primary, generatedColumns, generatedCubes); } finally { WorldgenHangWatchdog.endWorldGen(); } @@ -418,9 +436,15 @@ public void populate(Cube cube) { .recalculateStagingHeightmap(); } } + } + + for (Vector3ic v : getCubesToPopulate(cx, cy, cz)) { + Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); + + if (!center.isPopulated()) { + decorator.populate(world, center); - for (int dx = -1; dx <= 0; dx++) { - for (int dz = -1; dz <= 0; dz++) { + if (v.y() == 0) { // For some bizarre reason, MC offsets its block positions by +8 when populating. This means // that when a chunk // gets populated, it's actually populating the 16x16 blocks centered on the +x/+z corner. This @@ -429,17 +453,11 @@ public void populate(Cube cube) { // columns in the // negative directions (-x,z, x,-z, -x,-z). - populateChunk(loader, cx + dx, cz + dz); + populateChunk(loader, v.x(), v.z()); } - } - } - - for (Vector3ic v : getCubesToPopulate(cx, cy, cz)) { - Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); - decorator.populate(world, center); - - center.markPopulated(Cube.POP_000); + center.markPopulated(Cube.POP_000); + } } for (Vector3ic v : getFullyPopulatedCubes(cx, cy, cz)) { @@ -448,23 +466,6 @@ public void populate(Cube cube) { center.markPopulated(Cube.POP_ALL); loader.onCubeGenerated(center); } - - if (!cube.isFullyPopulated()) { - for (Vector3ic v : AFFECTED_CUBES) { - Cube adj = loader.getCube(cx + v.x(), cy + v.y(), cz + v.z(), Requirement.GENERATE); - - if (!adj.isPopulated()) { - CubicChunks.LOGGER.error( - "Failed to populate cube {},{},{}: requires cube: {},{},{}", - cx, - cy, - cz, - cx + v.x(), - cy + v.y(), - cz + v.z()); - } - } - } } catch (Throwable t) { CubicChunks.LOGGER.error("Could not run non-vanilla population for cube {},{},{}", cx, cy, cz, t); } finally { @@ -490,9 +491,9 @@ private Box getCubesToGenerate(int x, int y, int z) { private Box getCubesToPopulate(int x, int y, int z) { if (y >= 0 && y < 16) { - return new Box(x - 1, -1, x - 1, x, 15, z); + return new Box(x - 1, -1, z - 1, x, 15, z); } else { - return new Box(x - 1, y - 1, x - 1, x, y, z); + return new Box(x - 1, y - 1, z - 1, x, y, z); } } @@ -507,8 +508,6 @@ private Box getFullyPopulatedCubes(int x, int y, int z) { private void populateChunk(ICubeLoader loader, int columnX, int columnZ) { Chunk column = loader.getColumn(columnX, columnZ, Requirement.GENERATE); - if (column.isTerrainPopulated) return; - column.isTerrainPopulated = true; column.isModified = true; @@ -574,9 +573,6 @@ public void onCubePreloadFailed(CubePos pos, CubeInitLevel actual, CubeInitLevel } } - @Override - public void recreateStructures(ICube cube) {} - @Override public List getPossibleCreatures(EnumCreatureType creatureType, int x, int y, int z) { return vanilla.getPossibleCreatures(creatureType, x, y, z); diff --git a/src/main/resources/assets/cubicchunks/lang/en_US.lang b/src/main/resources/assets/cubicchunks/lang/en_US.lang index 52306d4f..e3b55d7b 100644 --- a/src/main/resources/assets/cubicchunks/lang/en_US.lang +++ b/src/main/resources/assets/cubicchunks/lang/en_US.lang @@ -25,6 +25,7 @@ cubicchunks.command.config.set.done=Config option %s has been set to "%s" cubicchunks.command.config.set.requires_restart=Changing config option %s requires world restart! cubicchunks.command.usage.config.set.primitive=Config option %s has type %s and requires 1 parameter cubicchunks.command.config.reload.done=Cubic Chunks config has been reloaded +cubicchunks.config.enable_chunk_debugging=Enable Chunk Status Debugging cubicchunks.config.optimizations=Optimizations cubicchunks.config.optimizations.background_threads=Background Threads From f574dbb0d6d4759642aa22f1f512c06a7cb56f7e Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Thu, 27 Nov 2025 16:05:31 -0500 Subject: [PATCH 11/22] Make sure it compiles --- addon.gradle | 7 ++ dependencies.gradle | 70 +++++++++++++++++-- repositories.gradle | 35 ++++++++++ .../cardinalstar/cubicchunks/CubicChunks.java | 3 +- .../util/CubeStatusVisualizer.java | 2 +- 5 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 addon.gradle diff --git a/addon.gradle b/addon.gradle new file mode 100644 index 00000000..29106b4a --- /dev/null +++ b/addon.gradle @@ -0,0 +1,7 @@ + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} diff --git a/dependencies.gradle b/dependencies.gradle index 2e216481..46261a5d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,11 +34,15 @@ * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - implementation("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") + implementation("com.github.GTNewHorizons:GTNHLib:99.99.99:dev") implementation("com.github.GTNewHorizons:RegionLib:99.99.99:dev") - runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.4-GTNH:dev") + devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.39-GTNH:dev") -// runtimeOnlyNonPublishable(rfg.deobf("curse.maven:spark-361579:4271867")) + compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.7.0:dev") + compileOnly("com.falsepattern:endlessids-mc1.7.10:1.6.14:dev") + + // implementation("com.falsepattern:chunkapi-mc1.7.10:0.7.0-1-g67006b5-dirty:dev") + // implementation("com.falsepattern:endlessids-mc1.7.10:1.6.14-dirty:dev") compileOnly("org.jetbrains:annotations:26.0.2") @@ -46,9 +50,65 @@ dependencies { annotationProcessor('org.projectlombok:lombok:1.18.34') devOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.2.21-GTNH-daily:dev") - // devOnlyNonPublishable("com.github.GTNewHorizons:Angelica:1.0.0-beta56:dev") + // devOnlyNonPublishable("com.github.GTNewHorizons:Angelica:1.0.0-beta68:dev") devOnlyNonPublishable rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612") - devOnlyNonPublishable("com.github.GTNewHorizons:Realistic-World-Gen:alpha-1.5.0") + devOnlyNonPublishable("com.github.GTNewHorizons:Realistic-World-Gen:1.4.0") + +// devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.8:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:Railcraft:9.17.6:dev') + + // Adds various useful features +// devOnlyNonPublishable("com.github.GTNewHorizons:EnderCore:0.5.0:dev") + +// devOnlyNonPublishable('com.github.GTNewHorizons:ThaumicBoots:1.5.2:dev') { +// exclude module: "witchery" +// exclude module: "TCNEIAdditions" +// } +// devOnlyNonPublishable('com.github.GTNewHorizons:ThaumicBases:1.9.5:dev') {exclude module: "Baubles" } +// devOnlyNonPublishable('com.github.GTNewHorizons:ForgeMultipart:1.7.0:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:Botania:1.13.6-GTNH:api') +// devOnlyNonPublishable('com.github.GTNewHorizons:MagicBees:2.10.2-GTNH:api') +// devOnlyNonPublishable('com.github.GTNewHorizons:TinkersConstruct:1.14.12-GTNH:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:ForbiddenMagic:0.9.7-GTNH:dev') {exclude module: "Baubles" } +// devOnlyNonPublishable('com.github.GTNewHorizons:twilightforest:2.7.13:dev') +// devOnlyNonPublishable(rfg.deobf('thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev')) +// devOnlyNonPublishable(rfg.deobf('curse.maven:witchery-69673:2234410')) + +// devOnlyNonPublishable("com.github.GTNewHorizons:DuraDisplay:1.4.0:dev") +// devOnlyNonPublishable('com.github.GTNewHorizons:EnderIO:2.10.3:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:MatterManipulator:0.1.3-GTNH:dev') + +// devOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.122:dev") { transitive=false } + +// devOnlyNonPublishable("com.github.GTNewHorizons:Galacticraft:3.4.7-GTNH:dev") { +// exclude group: "com.github.GTNewHorizons", module: "GT5-Unofficial" +// } + +// devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev') + +// devOnlyNonPublishable rfg.deobf("curse.maven:extra-utilities-225561:2264384") +// devOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.7-GTNH:dev") +// +// devOnlyNonPublishable("com.github.GTNewHorizons:ForestryMC:4.11.2:dev") +// devOnlyNonPublishable('com.github.GTNewHorizons:neiaddons:1.17.0:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:MagicBees:2.10.2-GTNH:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:Binnie:2.6.0:dev') +// +// devOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.12-GTNH:dev") +// +// devOnlyNonPublishable("com.github.GTNewHorizons:StructureLib:1.4.24:dev") +// devOnlyNonPublishable("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI:1.3.0:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI2:2.3.15-1.7.10:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:waila:1.9.15:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-749-GTNH:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:AE2FluidCraft-Rework:1.5.26-gtnh:dev") +// devOnlyNonPublishable('com.github.GTNewHorizons:Yamcl:0.7.1:dev') +// devOnlyNonPublishable("com.github.GTNewHorizons:Postea:1.1.3:dev") + + testImplementation(platform('org.junit:junit-bom:5.9.2')) + testImplementation('org.junit.jupiter:junit-jupiter') } diff --git a/repositories.gradle b/repositories.gradle index e2bb17bf..7ee0aceb 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -5,5 +5,40 @@ repositories { maven { setUrl("https://oss.sonatype.org/service/local/repositories/snapshots/content/") } + exclusiveContent { + forRepository { + ivy { + name = 'CoreTweaks releases' + url = 'https://github.com/makamys/CoreTweaks/releases/download/' + patternLayout { + artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])' + } + metadataSources { + artifact() + } + } + } + filter { + includeGroup('CoreTweaks') + } + } + exclusiveContent { + forRepository { + maven { + name = 'glease' + url = 'https://maven.glease.net/repos/releases/' + } + } + filter { + includeGroup('net.glease') + } + } + maven { + name = "mavenpattern" + url = "https://mvn.falsepattern.com/releases" + content { + includeGroup("com.falsepattern") + } + } mavenLocal() } diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index afa4a314..214f03dc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -50,12 +50,13 @@ import com.cardinalstar.cubicchunks.network.NetworkChannel; import com.cardinalstar.cubicchunks.server.chunkio.RegionCubeStorage; import com.cardinalstar.cubicchunks.util.CompatHandler; +import com.cardinalstar.cubicchunks.util.Mods; import com.cardinalstar.cubicchunks.util.SideUtils; import com.cardinalstar.cubicchunks.world.worldgen.WorldGenerators; import com.cardinalstar.cubicchunks.worldgen.WorldgenHangWatchdog; +import com.falsepattern.chunk.api.DataRegistry; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; - import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java index 06b4e0c6..811c2ebe 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -58,7 +58,7 @@ public static void sync(ServerTickEvent event) { return; } - List boxes = new ArrayList<>(); + // List boxes = new ArrayList<>(); cubeStatus.forEach((pos, status) -> { // boxes.add(new VisualizedBox( From 0c0034543ba8d5c99e87a73f393c39f032253df3 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Thu, 27 Nov 2025 16:07:15 -0500 Subject: [PATCH 12/22] spotless --- .../cardinalstar/cubicchunks/CubicChunks.java | 1 + .../cardinalstar/cubicchunks/api/CCAPI.java | 10 +- .../cardinalstar/cubicchunks/api/ICube.java | 6 +- .../cubicchunks/async/TaskPool.java | 17 ++-- .../lighting/FirstLightProcessor.java | 3 +- .../early/common/MixinRegionFileCache.java | 2 +- .../mixin/early/common/MixinWorldServer.java | 2 +- .../worldgen/MixinChunkProviderGenerate.java | 8 +- .../network/PacketEncoderCubeBlockChange.java | 6 +- .../network/PacketEncoderCubes.java | 5 +- .../network/PacketEncoderHeightMapUpdate.java | 1 + .../cubicchunks/network/WorldEncoder.java | 3 +- .../server/CubeProviderServer.java | 19 ++-- .../server/CubicPlayerManager.java | 94 +++++++++++-------- .../server/chunkio/CCNBTUtils.java | 3 +- .../cubicchunks/server/chunkio/CubeIO.java | 37 ++++---- .../server/chunkio/CubeLoaderServer.java | 39 +++++--- .../server/chunkio/IONbtReader.java | 1 + .../server/chunkio/IONbtWriter.java | 3 +- .../server/chunkio/RegionCubeStorage.java | 64 +++++++------ .../cubicchunks/util/BlockPosSet.java | 1 + .../util/CubeStatusVisualizer.java | 36 ++++--- .../cubicchunks/util/DataUtils.java | 10 +- .../cubicchunks/util/biome3d/BiomeArray.java | 1 + .../util/biome3d/DynamicBiomeArray.java | 4 +- .../visibility/CuboidalCubeSelector.java | 2 +- .../cubicchunks/world/CubeSpawnerAnimals.java | 8 +- .../world/chunkloader/CubicChunkManager.java | 77 +++++++-------- .../world/core/ServerHeightMap.java | 1 + .../cubicchunks/world/cube/Cube.java | 9 +- .../worldgen/data/NoisePrecalculator.java | 1 + .../vanilla/PrecalcedVanillaOctaves.java | 30 +++--- .../worldgen/VanillaWorldGenerator.java | 21 +++-- 33 files changed, 301 insertions(+), 224 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java index 214f03dc..f6d07b8b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java +++ b/src/main/java/com/cardinalstar/cubicchunks/CubicChunks.java @@ -57,6 +57,7 @@ import com.falsepattern.chunk.api.DataRegistry; import com.gtnewhorizon.gtnhlib.config.ConfigException; import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; + import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java index 15db1186..190f5c44 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java @@ -18,7 +18,7 @@ @SuppressWarnings("unused") public final class CCAPI { - private CCAPI() { } + private CCAPI() {} public static ExtendedBlockStorage getBlockStorage(Chunk chunk, int yLevel) { ICube cube = ((IColumn) chunk).getCube(yLevel); @@ -31,7 +31,8 @@ public static ExtendedBlockStorage getBlockStorage(Chunk chunk, int yLevel) { /// Gets a loaded cube. Does not load or generate the cube. @Nullable public static ICube getLoadedCube(World world, int cubeX, int cubeY, int cubeZ) { - return ((ICubicWorld) world).getCubeCache().getLoadedCube(cubeX, cubeY, cubeZ); + return ((ICubicWorld) world).getCubeCache() + .getLoadedCube(cubeX, cubeY, cubeZ); } /// Gets a cube. Does terrain generation if the cube is not in memory and could not be loaded from disk. @@ -46,7 +47,7 @@ public static ICube getCube(World world, int cubeX, int cubeY, int cubeZ, Requir /// Gets all loaded cubes in a column. public static Collection getLoadedCubes(Chunk chunk) { - //noinspection unchecked + // noinspection unchecked return (Collection) ((IColumn) chunk).getLoadedCubes(); } @@ -54,7 +55,8 @@ public static Collection getLoadedCubes(Chunk chunk) { public static Iterable getLoadedBlockStorages(Chunk chunk) { return () -> new AbstractIterator<>() { - private final Iterator iter = ((IColumn) chunk).getLoadedCubes().iterator(); + private final Iterator iter = ((IColumn) chunk).getLoadedCubes() + .iterator(); @Override protected ExtendedBlockStorage computeNext() { diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java index 585b37cb..aed11f36 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java @@ -272,9 +272,9 @@ default boolean isInitializedToLevel(CubeInitLevel initLevel) { /** * Set biome at a cube-local 4x4x4 block segment. * - * @param x cube-local block X coordinate - * @param y cube-local block Y coordinate - * @param z cube-local block Z coordinate + * @param x cube-local block X coordinate + * @param y cube-local block Y coordinate + * @param z cube-local block Z coordinate * @param biome The biome at the given cube coordinates, or null to defer to the column */ void setBiome(int x, int y, int z, BiomeGenBase biome); diff --git a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java index 3c2b9d5c..82e3b8e0 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -81,7 +81,8 @@ public void run() { public static final ITaskExecutor RUNNABLE_EXECUTOR = tasks -> { for (var task : tasks) { - task.getTask().run(); + task.getTask() + .run(); task.finish(null); } }; @@ -94,7 +95,8 @@ public static Future submit(ITaskExecutor Future submit(ITaskExecutor executor, TTask task, @Nullable Consumer callback) { + public static Future submit(ITaskExecutor executor, TTask task, + @Nullable Consumer callback) { TaskFuture future = new TaskFuture<>(task, callback); synchronized (QUEUE_LOCK) { @@ -105,8 +107,8 @@ public static Future submit(ITaskExecutor end2 = (TaskContainer) end; - //noinspection unchecked - if (end2.executor.canMerge((List>) (List) end2.tasks, task)) { + // noinspection unchecked + if (end2.executor.canMerge((List>) (List) end2.tasks, task)) { end2.tasks.add(future); QUEUE_LOCK.notify(); return future; @@ -151,7 +153,8 @@ public interface ITaskFuture { void fail(Throwable t); } - private static class TaskFuture extends AbstractFuture implements ITaskFuture { + private static class TaskFuture extends AbstractFuture + implements ITaskFuture { public final TTask task; @Nullable @@ -193,8 +196,8 @@ public void run() { try { tasks.removeIf(TaskFuture::isCancelled); - //noinspection unchecked - executor.execute((List>) (List) tasks); + // noinspection unchecked + executor.execute((List>) (List) tasks); } catch (Exception e) { CubicChunks.LOGGER.error("Could not run background task", e); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java b/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java index aa715470..ba88f37e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java +++ b/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java @@ -75,7 +75,8 @@ public void diffuseSkylight(ICube cube) { BlockPos maxPos = cube.getCoords() .getMaxBlockPos(); - ICubeLoader loader = ((ICubicWorldInternal.Server) cube.getWorld()).getCubeCache().getCubeLoader(); + ICubeLoader loader = ((ICubicWorldInternal.Server) cube.getWorld()).getCubeCache() + .getCubeLoader(); loader.cacheCubes(cube.getX(), cube.getY(), cube.getZ(), 1, 1, 1); diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java index 64041054..b6efedb7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java @@ -39,6 +39,6 @@ public class MixinRegionFileCache { @Inject(method = "clearRegionFileReferences", at = @At("HEAD")) private static void onClearRefs(CallbackInfo cbi) throws IOException { -// SharedCachedRegionProvider.clearRegions(); + // SharedCachedRegionProvider.clearRegions(); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java index 760c5f65..83632a4e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldServer.java @@ -321,7 +321,7 @@ private void updateBlocksCubicChunks(CallbackInfo cbi) { boolean thundering = this.isThundering(); this.theProfiler.startSection("pollingChunks"); - for (Chunk chunk : ((CubeProviderServer) this.theChunkProviderServer).getTickableChunks()) { + for (Chunk chunk : ((CubeProviderServer) this.theChunkProviderServer).getTickableChunks()) { tickColumn(raining, thundering, chunk); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java index a6532a36..fe95ee91 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/worldgen/MixinChunkProviderGenerate.java @@ -16,6 +16,7 @@ import com.cardinalstar.cubicchunks.world.worldgen.vanilla.PrecalculableNoise; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -35,9 +36,10 @@ public class MixinChunkProviderGenerate implements Precalculable { public NoiseGeneratorOctaves noiseGen6; @WrapOperation( - method = "", at = @At( - value = "NEW", target = "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) - public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves, Operation original) { + method = "", + at = @At(value = "NEW", target = "(Ljava/util/Random;I)Lnet/minecraft/world/gen/NoiseGeneratorOctaves;")) + public NoiseGeneratorOctaves usePregenerateNoise(Random random, int octaves, + Operation original) { return new PrecalcedVanillaOctaves(original.call(random, octaves)); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index fa65f7fb..f9df94b7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -44,6 +44,7 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.falsepattern.chunk.internal.DataRegistryImpl; import com.github.bsideup.jabel.Desugar; + import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import gnu.trove.iterator.TIntIterator; @@ -54,7 +55,8 @@ public class PacketEncoderCubeBlockChange extends CCPacketEncoder { @Desugar - public record PacketCubeBlockChange(CubePos cubePos, int[] heightValues, List updates) implements CCPacket { + public record PacketCubeBlockChange(CubePos cubePos, int[] heightValues, List updates) + implements CCPacket { @Override public byte getPacketID() { @@ -157,7 +159,7 @@ public PacketCubeBlockChange readPacket(CCPacketBuffer buffer) { } @Override -@SideOnly(Side.CLIENT) + @SideOnly(Side.CLIENT) public void process(World world, PacketCubeBlockChange packet) { WorldClient worldClient = (WorldClient) world; CubeProviderClient cubeCache = (CubeProviderClient) worldClient.getChunkProvider(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java index 3d0cbb01..fba5fecf 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubes.java @@ -68,7 +68,10 @@ public static PacketCubes createPacket(List cubes) { for (int i = 0; i < cubes.size(); i++) { cubePos[i] = cubes.get(i) .getCoords(); - CubeStatusVisualizer.put(cubes.get(i).getCoords(), CubeStatus.Synced); + CubeStatusVisualizer.put( + cubes.get(i) + .getCoords(), + CubeStatus.Synced); } ByteBuf cubeData = Unpooled.buffer(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java index c828fa6b..4ba098be 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java @@ -38,6 +38,7 @@ import com.cardinalstar.cubicchunks.world.core.ClientHeightMap; import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.github.bsideup.jabel.Desugar; + import it.unimi.dsi.fastutil.ints.IntArrayList; @ParametersAreNonnullByDefault diff --git a/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java b/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java index 3074f16b..c3316242 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/WorldEncoder.java @@ -228,7 +228,8 @@ static void decodeCube(CCPacketBuffer in, List cubes) { try { // DataRegistryImpl.readFromBufferCubic(cube.getColumn(), storage, in.readByteArray(buffer)); } catch (Throwable t) { - CubicChunks.LOGGER.error("Error decoding ChunkAPI data ({},{},{})", cube.getX(), cube.getY(), cube.getZ(), t); + CubicChunks.LOGGER + .error("Error decoding ChunkAPI data ({},{},{})", cube.getX(), cube.getY(), cube.getZ(), t); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java index 79445f79..5fddbe5b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubeProviderServer.java @@ -68,6 +68,7 @@ import com.cardinalstar.cubicchunks.world.savedata.WorldFormatSavedData; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; + import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; import it.unimi.dsi.fastutil.ints.IntComparator; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -216,7 +217,7 @@ private List getPlayers() { // noinspection ConstantValue if (worldServer == null) return Collections.emptyList(); - //noinspection unchecked + // noinspection unchecked return (List) (List) worldServer.playerEntities; } @@ -325,7 +326,9 @@ private void doEagerLoading() { profiler.startSection("Eager object sorting"); // TODO: make this faster - eagerLoadOrder.sort(Comparator.comparingInt(this::getChunkDistanceSquared).reversed()); + eagerLoadOrder.sort( + Comparator.comparingInt(this::getChunkDistanceSquared) + .reversed()); profiler.endStartSection("Eager object loading"); @@ -345,7 +348,8 @@ private void doEagerLoading() { continue; } - Iterator cubeIter = container.cubes.values().iterator(); + Iterator cubeIter = container.cubes.values() + .iterator(); while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { EagerCubeLoadRequest request = cubeIter.next(); @@ -359,7 +363,8 @@ private void doEagerLoading() { cubeLoader.pauseLoadCalls(); - Cube cube = cubeLoader.getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); + Cube cube = cubeLoader + .getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); cubeLoader.unpauseLoadCalls(); @@ -392,7 +397,8 @@ private void doEagerLoading() { } if (processed > 0) { - CubicChunks.LOGGER.info("Processed {} eager load requests this tick ({} -> {} columns)", + CubicChunks.LOGGER.info( + "Processed {} eager load requests this tick ({} -> {} columns)", processed, startCols, eagerLoadOrder.size()); @@ -456,7 +462,8 @@ public static class EagerCubeLoadContainer implements XZAddressable { public final ChunkCoordIntPair pos; - public final Int2ObjectRBTreeMap cubes = new Int2ObjectRBTreeMap<>(IntComparator.comparingInt(i -> -i)); + public final Int2ObjectRBTreeMap cubes = new Int2ObjectRBTreeMap<>( + IntComparator.comparingInt(i -> -i)); public EagerCubeLoadContainer(ChunkCoordIntPair pos) { this.pos = pos; diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index bd108410..a69898ab 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -76,6 +76,7 @@ import com.cardinalstar.cubicchunks.world.cube.Cube; import com.google.common.collect.AbstractIterator; import com.google.common.collect.Iterators; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -263,7 +264,8 @@ private void syncColumns() { } private void syncCubes() { - ((ICubicWorldInternal) getWorldServer()).getLightingManager().onSendCubes(() -> Iterators.transform(dirtyCubes.iterator(), c -> c.cube)); + ((ICubicWorldInternal) getWorldServer()).getLightingManager() + .onSendCubes(() -> Iterators.transform(dirtyCubes.iterator(), c -> c.cube)); List fullSync = new ObjectArrayList<>(); @@ -313,7 +315,8 @@ private void syncCubes() { @Override public void onColumnLoaded(Chunk column) { - WatchedColumn watcher = this.getOrCreateWatchedColumn(new ChunkCoordIntPair(column.xPosition, column.zPosition)); + WatchedColumn watcher = this + .getOrCreateWatchedColumn(new ChunkCoordIntPair(column.xPosition, column.zPosition)); if (watcher != null) { watcher.setColumn(column); @@ -458,13 +461,14 @@ public void addPlayer(EntityPlayerMP player) { CubePos playerCubePos = CubePos.fromEntity(player); - CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (currentPos) -> { - // create cubeWatcher and chunkWatcher - // order is important + CuboidalCubeSelector.INSTANCE + .forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (currentPos) -> { + // create cubeWatcher and chunkWatcher + // order is important - getOrCreateWatchedColumn(currentPos.chunkPos()).addPlayer(watchingPlayer); - getOrCreateCubeWatcher(currentPos).addPlayer(watchingPlayer); - }); + getOrCreateWatchedColumn(currentPos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(currentPos).addPlayer(watchingPlayer); + }); watchingPlayer.flushCubes(); @@ -489,23 +493,24 @@ public void removePlayer(EntityPlayerMP player) { ObjectSet unloadedColumns = new ObjectOpenHashSet<>( (horizontalViewDistance * 2 + 1) * (horizontalViewDistance * 2 + 1)); - CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> { - // get the watcher - WatchedCube cube = watchedCubes.get(cubePos); + CuboidalCubeSelector.INSTANCE + .forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> { + // get the watcher + WatchedCube cube = watchedCubes.get(cubePos); - if (cube != null) { - // Cube will be GC'd if it isn't watched by a player - cube.removePlayer(watchingPlayer); - } + if (cube != null) { + // Cube will be GC'd if it isn't watched by a player + cube.removePlayer(watchingPlayer); + } - // remove column watchers if needed - WatchedColumn column = watchedColumns.get(cubePos.getX(), cubePos.getZ()); + // remove column watchers if needed + WatchedColumn column = watchedColumns.get(cubePos.getX(), cubePos.getZ()); - if (column != null) { - // Column will be GC'd if it isn't watched by a player - unloadedColumns.add(column); - } - }); + if (column != null) { + // Column will be GC'd if it isn't watched by a player + unloadedColumns.add(column); + } + }); for (WatchedColumn watcher : unloadedColumns) { watcher.removePlayer(watchingPlayer); @@ -581,14 +586,18 @@ private void updatePlayer(WatchingPlayer player, CubePos oldPos, CubePos newPos) // order is important, columns first getWorldServer().theProfiler.endStartSection("createColumns"); - columnsToLoad.forEach(pos -> { - this.getOrCreateWatchedColumn(pos).addPlayer(player); - }); + columnsToLoad.forEach( + pos -> { + this.getOrCreateWatchedColumn(pos) + .addPlayer(player); + }); getWorldServer().theProfiler.endStartSection("createCubes"); - cubesToLoad.forEach(pos -> { - this.getOrCreateCubeWatcher(pos).addPlayer(player); - }); + cubesToLoad.forEach( + pos -> { + this.getOrCreateCubeWatcher(pos) + .addPlayer(player); + }); getWorldServer().theProfiler.endStartSection("removeCubes"); cubesToRemove.forEach(pos -> { @@ -614,11 +623,7 @@ private void updatePlayer(WatchingPlayer player, CubePos oldPos, CubePos newPos) // Force load the cube the player is in along with its 26 neighbours for (Vector3ic v : new Box(-1, -1, -1, 1, 1, 1)) { - cubeCache.getCube( - newPos.getX() + v.x(), - newPos.getY() + v.y(), - newPos.getZ() + v.z(), - Requirement.LIGHT); + cubeCache.getCube(newPos.getX() + v.x(), newPos.getY() + v.y(), newPos.getZ() + v.z(), Requirement.LIGHT); } getWorldServer().theProfiler.endSection();// Immediate nearby cube loading @@ -712,10 +717,11 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe if (newHorizontalViewDistance > oldHorizontalViewDistance || newVerticalViewDistance > oldVerticalViewDistance) { // if newRadius is bigger, we only need to load new cubes - CuboidalCubeSelector.INSTANCE.forAllVisibleFrom(playerPos, newHorizontalViewDistance, newVerticalViewDistance, pos -> { - getOrCreateWatchedColumn(pos.chunkPos()).addPlayer(watchingPlayer); - getOrCreateCubeWatcher(pos).addPlayer(watchingPlayer); - }); + CuboidalCubeSelector.INSTANCE + .forAllVisibleFrom(playerPos, newHorizontalViewDistance, newVerticalViewDistance, pos -> { + getOrCreateWatchedColumn(pos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(pos).addPlayer(watchingPlayer); + }); } else { // either both got smaller or only one of them changed Set cubesToUnload = new HashSet<>(); @@ -766,7 +772,8 @@ public void queueCube(Cube cube) { public void flushCubes() { if (!cubeSendQueue.isEmpty()) { - PacketEncoderCubes.createPacket(cubeSendQueue).sendToPlayer(player); + PacketEncoderCubes.createPacket(cubeSendQueue) + .sendToPlayer(player); cubeSendQueue.clear(); } } @@ -808,6 +815,7 @@ enum Dirtiness { } private class WatchedColumn extends ChunkCoordIntPair implements XZAddressable { + public Chunk column; public final BooleanArray2D dirtyColumns = new BooleanArray2D(16, 16); public final ReferenceOpenHashSet watchingPlayers = new ReferenceOpenHashSet<>(4); @@ -836,7 +844,8 @@ public void addPlayer(WatchingPlayer player) { watchingPlayers.add(player); if (column != null) { - PacketEncoderColumn.createPacket(column).sendToPlayer(player.player); + PacketEncoderColumn.createPacket(column) + .sendToPlayer(player.player); } } @@ -846,7 +855,8 @@ public void removePlayer(WatchingPlayer player) { watchingPlayers.remove(player); if (column != null) { - PacketEncoderUnloadColumn.createPacket(chunkXPos, chunkZPos).sendToPlayer(player.player); + PacketEncoderUnloadColumn.createPacket(chunkXPos, chunkZPos) + .sendToPlayer(player.player); } } @@ -905,6 +915,7 @@ public int getZ() { } private class WatchedCube extends CubePos { + public EagerCubeLoadRequest request; public Cube cube; public final ShortArrayList dirtyBlocks = new ShortArrayList(8); @@ -976,7 +987,8 @@ public void removePlayer(WatchingPlayer player) { watchingPlayers.remove(player); if (cube != null) { - PacketEncoderUnloadCube.createPacket(cube.getCoords()).sendToPlayer(player.player); + PacketEncoderUnloadCube.createPacket(cube.getCoords()) + .sendToPlayer(player.player); } if (watchingPlayers.isEmpty() && this.request != null) this.request.cancel(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java index 94c0ad5f..002f7273 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java @@ -23,7 +23,8 @@ public static NBTTagCompound loadTag(byte[] data) throws IOException { } } - return CompressedStreamTools.func_152456_a(new DataInputStream(new ByteArrayInputStream(data)), NBTSizeTracker.field_152451_a); + return CompressedStreamTools + .func_152456_a(new DataInputStream(new ByteArrayInputStream(data)), NBTSizeTracker.field_152451_a); } public static byte[] saveTag(NBTTagCompound tag, boolean compress) throws IOException { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index 4ea880f5..70c52a77 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -26,6 +26,7 @@ import com.cardinalstar.cubicchunks.util.DataUtils; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; + import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AllArgsConstructor; @@ -33,7 +34,8 @@ public class CubeIO implements ICubeIO { - private static final long EXPIRY = Duration.ofSeconds(120).toMillis(); + private static final long EXPIRY = Duration.ofSeconds(120) + .toMillis(); private final ICubicStorage storage; private final IPreloadFailureDelegate preloadFailures; @@ -45,16 +47,19 @@ public class CubeIO implements ICubeIO { private final Object2ObjectLinkedOpenHashMap cubeCache = new Object2ObjectLinkedOpenHashMap<>(); - interface Savable { } + interface Savable { + } @Desugar - private record SaveColumn(ChunkCoordIntPair pos, NBTTagCompound tag) implements Savable { } + private record SaveColumn(ChunkCoordIntPair pos, NBTTagCompound tag) implements Savable {} + @Desugar - private record SaveCube(CubePos pos, NBTTagCompound tag) implements Savable { } + private record SaveCube(CubePos pos, NBTTagCompound tag) implements Savable {} @NoArgsConstructor @AllArgsConstructor private static class SaveData { + @Nullable public NBTTagCompound tag; @Nullable @@ -68,10 +73,8 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { columnLoadExecutor = tasks -> { try { - var result = storage.readBatch( - new PosBatch( - DataUtils.mapToList(tasks, ITaskFuture::getTask), - Collections.emptyList())); + var result = storage + .readBatch(new PosBatch(DataUtils.mapToList(tasks, ITaskFuture::getTask), Collections.emptyList())); for (var task : tasks) { NBTTagCompound tag = result.columns.get(task.getTask()); @@ -87,10 +90,8 @@ public CubeIO(ICubicStorage storage, IPreloadFailureDelegate preloadFailures) { cubeLoadExecutor = tasks -> { try { - var result = storage.readBatch( - new PosBatch( - Collections.emptyList(), - DataUtils.mapToList(tasks, ITaskFuture::getTask))); + var result = storage + .readBatch(new PosBatch(Collections.emptyList(), DataUtils.mapToList(tasks, ITaskFuture::getTask))); for (var task : tasks) { NBTTagCompound tag = result.cubes.get(task.getTask()); @@ -255,7 +256,8 @@ public void saveColumn(ChunkCoordIntPair pos, Chunk column) { if (columnCache.size() > 5000) { int i = 0; - var iter = columnCache.object2ObjectEntrySet().fastIterator(); + var iter = columnCache.object2ObjectEntrySet() + .fastIterator(); while (columnCache.size() > 5000 && i++ < 100) { var e = iter.next(); @@ -295,7 +297,8 @@ public void saveCube(CubePos pos, Cube cube) { if (cubeCache.size() > 30000) { int i = 0; - var iter = cubeCache.object2ObjectEntrySet().fastIterator(); + var iter = cubeCache.object2ObjectEntrySet() + .fastIterator(); while (cubeCache.size() > 30000 && i++ < 100) { var e = iter.next(); @@ -330,7 +333,8 @@ public void close() throws IOException { synchronized (columnCache) { if (columnCache.isEmpty()) break; - columnCache.object2ObjectEntrySet().removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + columnCache.object2ObjectEntrySet() + .removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); } Thread.yield(); @@ -340,7 +344,8 @@ public void close() throws IOException { synchronized (cubeCache) { if (cubeCache.isEmpty()) break; - cubeCache.object2ObjectEntrySet().removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + cubeCache.object2ObjectEntrySet() + .removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); } Thread.yield(); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index 096bf991..d1e33b08 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -33,12 +33,13 @@ import com.cardinalstar.cubicchunks.world.core.IColumnInternal; import com.cardinalstar.cubicchunks.world.cube.BlankCube; import com.cardinalstar.cubicchunks.world.cube.Cube; + import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Setter; public class CubeLoaderServer implements ICubeLoader { - private static final MetaKey CUBE_INFO = new MetaKey<>() { }; + private static final MetaKey CUBE_INFO = new MetaKey<>() {}; private final WorldServer world; private final ICubeIO cubeIO; @@ -384,9 +385,14 @@ public void doGC() { unloadColumn(column); } - CubicChunks.LOGGER.info("Garbage collected {} columns ({} -> {}) and {} cubes ({} -> {}). Removed {} columns automatically because they were empty.", - pendingColumnUnloads.size(), startCols, columns.getSize(), - pendingCubeUnloads.size(), startCubes, cubes.getSize(), + CubicChunks.LOGGER.info( + "Garbage collected {} columns ({} -> {}) and {} cubes ({} -> {}). Removed {} columns automatically because they were empty.", + pendingColumnUnloads.size(), + startCols, + columns.getSize(), + pendingCubeUnloads.size(), + startCubes, + cubes.getSize(), startCols - autoCols); } @@ -406,7 +412,8 @@ private void handleSideEffects(GenerationResult result, int colX, int colZ, b ColumnInfo info = columns.get(column.xPosition, column.zPosition); if (info != null && info.column != null) { - CubicChunks.LOGGER.warn("Worldgen side-effect replaced column at {},{}!", column.xPosition, column.zPosition); + CubicChunks.LOGGER + .warn("Worldgen side-effect replaced column at {},{}!", column.xPosition, column.zPosition); continue; } @@ -427,7 +434,8 @@ private void handleSideEffects(GenerationResult result, int colX, int colZ, b CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); if (info != null && info.cube != null) { - CubicChunks.LOGGER.warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); + CubicChunks.LOGGER + .warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); continue; } @@ -645,11 +653,17 @@ private boolean loadNBT() { } private void loadCube() throws IOException { - // Cubes should always have a containing column unless someone's deleting files. Try to load it, or fail if the column is missing. + // Cubes should always have a containing column unless someone's deleting files. Try to load it, or fail if + // the column is missing. ensureColumn(Requirement.LOAD); if (this.column == null) { - CubicChunks.LOGGER.error("Tried to load a cube that did not have a saved column: it will be regenerated ({},{},{})", getX(), getY(), getZ(), new Exception()); + CubicChunks.LOGGER.error( + "Tried to load a cube that did not have a saved column: it will be regenerated ({},{},{})", + getX(), + getY(), + getZ(), + new Exception()); this.cube = null; this.tag = null; return; @@ -688,14 +702,16 @@ private boolean generate(CubeInitLevel requestedInitLevel) { "Cannot recursively generate a cube that is already being generated"); } - // Try to get any columns that already exist, since the generator may re-generate it if it thinks the column is missing. + // Try to get any columns that already exist, since the generator may re-generate it if it thinks the + // column is missing. ensureColumn(Requirement.LOAD); GenerationResult result; try { this.generating = true; - result = generator.provideCube(column == null ? null : column.column, pos.getX(), pos.getY(), pos.getZ()); + result = generator + .provideCube(column == null ? null : column.column, pos.getX(), pos.getY(), pos.getZ()); } finally { this.generating = false; } @@ -713,7 +729,8 @@ private boolean generate(CubeInitLevel requestedInitLevel) { ensureColumn(Requirement.GET_CACHED); if (this.column == null) { - throw new IllegalStateException("Generated a cube without generating its column: this is an invalid state"); + throw new IllegalStateException( + "Generated a cube without generating its column: this is an invalid state"); } // Alert everything that this cube was generated diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java index f4c48708..18bfb356 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java @@ -52,6 +52,7 @@ import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.falsepattern.chunk.internal.DataRegistryImpl; + import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java index 7bafae22..c77d9a8d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtWriter.java @@ -52,6 +52,7 @@ import com.cardinalstar.cubicchunks.world.core.ServerHeightMap; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.falsepattern.chunk.internal.DataRegistryImpl; + import cpw.mods.fml.common.FMLLog; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; @@ -86,7 +87,7 @@ static NBTTagCompound write(final Cube cube) { NBTTagCompound level = new NBTTagCompound(); cubeNbt.setTag("Level", level); writeBaseCube(cube, level); - + if (cube.getStorage() != null) { NBTTagList sections = new NBTTagList(); level.setTag("Sections", sections); diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java index 825951e2..4d3da278 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/RegionCubeStorage.java @@ -44,6 +44,7 @@ import com.cardinalstar.cubicchunks.server.chunkio.region.ShadowPagingRegion; import com.cardinalstar.cubicchunks.util.CubePos; import com.cardinalstar.cubicchunks.util.DataUtils; + import cubicchunks.regionlib.impl.EntryLocation2D; import cubicchunks.regionlib.impl.EntryLocation3D; import cubicchunks.regionlib.impl.SaveCubeColumns; @@ -85,18 +86,14 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists( - part2d.resolve( - key.getName())))), + (dir, key) -> Files.exists(part2d.resolve(key.getName())))), new SharedCachedRegionProvider<>( new SimpleRegionFactory<>( new EntryLocation2D.Provider(), part2d, (keyProvider, regionKey) -> new ExtRegion<>(part2d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists( - part2d.resolve( - key.getName() + ".ext"))))); + (dir, key) -> Files.exists(part2d.resolve(key.getName() + ".ext"))))); @SuppressWarnings("unchecked") SaveSection3D section3d = new SaveSection3D( new SharedCachedRegionProvider<>( @@ -109,18 +106,14 @@ private static SaveCubeColumns saveForPath(Path path) throws IOException { .setKeyProvider(keyProv) .setSectorSize(512) .build(), - (dir, key) -> Files.exists( - part3d.resolve( - key.getName())))), + (dir, key) -> Files.exists(part3d.resolve(key.getName())))), new SharedCachedRegionProvider<>( new SimpleRegionFactory<>( new EntryLocation3D.Provider(), part3d, (keyProvider, regionKey) -> new ExtRegion<>(part3d, Collections.emptyList(), keyProvider, regionKey), - (dir, key) -> Files.exists( - part3d.resolve( - key.getName() + ".ext"))))); + (dir, key) -> Files.exists(part3d.resolve(key.getName() + ".ext"))))); return new SaveCubeColumns(section2d, section3d); } else { @@ -156,7 +149,9 @@ public NBTTagCompound readColumn(ChunkCoordIntPair pos) throws IOException { Optional data = this.save.load(new EntryLocation2D(pos.chunkXPos, pos.chunkZPos), true); if (!data.isPresent()) return null; - return CCNBTUtils.loadTag(data.get().array()); + return CCNBTUtils.loadTag( + data.get() + .array()); } @Override @@ -165,21 +160,32 @@ public NBTTagCompound readCube(CubePos pos) throws IOException { Optional data = this.save.load(new EntryLocation3D(pos.getX(), pos.getY(), pos.getZ()), true); if (!data.isPresent()) return null; - return CCNBTUtils.loadTag(data.get().array()); + return CCNBTUtils.loadTag( + data.get() + .array()); } @Override public @NotNull NBTBatch readBatch(PosBatch positions) throws IOException { - var columns = this.save.load2D(DataUtils.mapToList(positions.columns, c -> new EntryLocation2D(c.chunkXPos, c.chunkZPos)), false); - var cubes = this.save.load3D(DataUtils.mapToList(positions.cubes, c -> new EntryLocation3D(c.getX(), c.getY(), c.getZ())), false); + var columns = this.save + .load2D(DataUtils.mapToList(positions.columns, c -> new EntryLocation2D(c.chunkXPos, c.chunkZPos)), false); + var cubes = this.save.load3D( + DataUtils.mapToList(positions.cubes, c -> new EntryLocation3D(c.getX(), c.getY(), c.getZ())), + false); var columnTags = columns.read.entrySet() .parallelStream() .map(e -> { try { return Pair.of( - new ChunkCoordIntPair(e.getKey().getEntryX(), e.getKey().getEntryZ()), - CCNBTUtils.loadTag(e.getValue().array())); + new ChunkCoordIntPair( + e.getKey() + .getEntryX(), + e.getKey() + .getEntryZ()), + CCNBTUtils.loadTag( + e.getValue() + .array())); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -191,8 +197,16 @@ public NBTTagCompound readCube(CubePos pos) throws IOException { .map(e -> { try { return Pair.of( - new CubePos(e.getKey().getEntryX(), e.getKey().getEntryY(), e.getKey().getEntryZ()), - CCNBTUtils.loadTag(e.getValue().array())); + new CubePos( + e.getKey() + .getEntryX(), + e.getKey() + .getEntryY(), + e.getKey() + .getEntryZ()), + CCNBTUtils.loadTag( + e.getValue() + .array())); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -245,19 +259,13 @@ public void writeBatch(NBTBatch batch) throws IOException { this.save.save2d( compressedColumns.entrySet() .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> ByteBuffer.wrap(entry.getValue())))); + .collect(Collectors.toMap(Map.Entry::getKey, entry -> ByteBuffer.wrap(entry.getValue())))); } if (!compressedCubes.isEmpty()) { this.save.save3d( compressedCubes.entrySet() .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> ByteBuffer.wrap(entry.getValue())))); + .collect(Collectors.toMap(Map.Entry::getKey, entry -> ByteBuffer.wrap(entry.getValue())))); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java index c6d001a3..53f09904 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java @@ -8,6 +8,7 @@ import org.joml.Vector3ic; import com.cardinalstar.cubicchunks.api.XYZAddressable; + import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java index 811c2ebe..5615abc8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -1,21 +1,15 @@ package com.cardinalstar.cubicchunks.util; import java.awt.*; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; -import org.joml.primitives.AABBd; - import com.cardinalstar.cubicchunks.CubicChunksConfig; import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; -// import com.gtnewhorizon.gtnhlib.visualization.BoxVisualizer; -// import com.gtnewhorizon.gtnhlib.visualization.VisualizedBox; + import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent; @@ -50,7 +44,8 @@ public static void sync(ServerTickEvent event) { if (!CubicChunksConfig.enableChunkStatusDebugging) { if (wasSent) { wasSent = false; - for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + for (EntityPlayerMP player : MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) { // BoxVisualizer.sendBoxes(player, Duration.ofMinutes(0), new ArrayList<>(), true); } } @@ -62,23 +57,24 @@ public static void sync(ServerTickEvent event) { cubeStatus.forEach((pos, status) -> { // boxes.add(new VisualizedBox( - // switch (status) { - // case None -> new Color(100, 50, 100, 50); - // case Generated -> new Color(50, 200, 50, 50); - // case Populated -> new Color(50, 50, 200, 50); - // case Lit -> new Color(200, 200, 50, 50); - // case Dirty -> new Color(14, 229, 187, 50); - // case Synced -> new Color(200, 50, 50, 50); - // }, - // new AABBd( - // pos.getMinBlockX() - 0.5, pos.getMinBlockY() + 0.5, pos.getMinBlockZ() - 0.5, - // pos.getMaxBlockX() - 0.5, pos.getMaxBlockY() - 0.5, pos.getMaxBlockZ() - 0.5) + // switch (status) { + // case None -> new Color(100, 50, 100, 50); + // case Generated -> new Color(50, 200, 50, 50); + // case Populated -> new Color(50, 50, 200, 50); + // case Lit -> new Color(200, 200, 50, 50); + // case Dirty -> new Color(14, 229, 187, 50); + // case Synced -> new Color(200, 50, 50, 50); + // }, + // new AABBd( + // pos.getMinBlockX() - 0.5, pos.getMinBlockY() + 0.5, pos.getMinBlockZ() - 0.5, + // pos.getMaxBlockX() - 0.5, pos.getMaxBlockY() - 0.5, pos.getMaxBlockZ() - 0.5) // )); }); wasSent = true; - for (EntityPlayerMP player : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + for (EntityPlayerMP player : MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) { // BoxVisualizer.sendBoxes(player, Duration.ofMinutes(5), boxes, true); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java index 1f70ced1..20322d64 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java @@ -76,7 +76,9 @@ public static int indexOf(T[] array, T value) { int l = array.length; for (int i = 0; i < l; i++) { - if (array[i] == value) { return i; } + if (array[i] == value) { + return i; + } } return -1; @@ -86,7 +88,9 @@ public static int indexOf(T[] array, Predicate filter) { int l = array.length; for (int i = 0; i < l; i++) { - if (filter.test(array[i])) { return i; } + if (filter.test(array[i])) { + return i; + } } return -1; @@ -222,7 +226,7 @@ public static Function exposeFieldGetterLambda(Class cla return instance -> { try { - //noinspection unchecked + // noinspection unchecked return (R) method.invokeExact(instance); } catch (Throwable e) { throw new RuntimeException("Could not get field " + clazz.getName() + ":" + names[0], e); diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java index 489d385f..dda2f112 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import com.cardinalstar.cubicchunks.util.AddressTools; + import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; public interface BiomeArray extends Int2ObjectFunction { diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java index 6b3594e0..e5e6daf1 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java @@ -3,6 +3,7 @@ import net.minecraft.world.biome.BiomeGenBase; import com.cardinalstar.cubicchunks.network.CCPacketBuffer; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -135,7 +136,8 @@ public void read(CCPacketBuffer buffer) { i += len; } default -> { - throw new InvalidBiomeDataException("Unexpected operation " + insn + " at index" + buffer.readerIndex()); + throw new InvalidBiomeDataException( + "Unexpected operation " + insn + " at index" + buffer.readerIndex()); } } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java b/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java index 90029b10..361e40d6 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java +++ b/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java @@ -34,7 +34,7 @@ public class CuboidalCubeSelector extends CubeSelector { public static final CuboidalCubeSelector INSTANCE = new CuboidalCubeSelector(); - private CuboidalCubeSelector() { } + private CuboidalCubeSelector() {} @Override public void forAllVisibleFrom(CubePos cubePos, int horizontalViewDistance, int verticalViewDistance, diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java b/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java index f94fd26b..b42479d8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/CubeSpawnerAnimals.java @@ -52,6 +52,7 @@ import com.cardinalstar.cubicchunks.util.MathUtil; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; + import cpw.mods.fml.common.eventhandler.Event; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; @@ -94,7 +95,9 @@ public int findChunksForSpawning(WorldServer world, boolean hostileEnable, boole LongLists.shuffle(shuffled, world.rand); shuffled = shuffled.subList(0, Math.min(this.cubesForSpawn.size(), 2 * (2 * SPAWN_RADIUS + 1))); - List cubes = shuffled.longStream().mapToObj(CubePos::unpack).collect(Collectors.toList()); + List cubes = shuffled.longStream() + .mapToObj(CubePos::unpack) + .collect(Collectors.toList()); totalSpawnCount += spawnCreatureTypeInAllChunks(mobType, world, cubes); } @@ -116,7 +119,8 @@ private int addEligibleCubes(WorldServer world, BlockPosSet possibleCubes) { assert !possibleCubes.contains(v.x(), v.y(), v.z()); cubeCount++; - boolean valid = ((CubicPlayerManager) world.getPlayerManager()).isCubeWatchedAndPresent(v.x(), v.y(), v.z()); + boolean valid = ((CubicPlayerManager) world.getPlayerManager()) + .isCubeWatchedAndPresent(v.x(), v.y(), v.z()); if (valid) { possibleCubes.add(v.x(), v.y(), v.z()); diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java b/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java index 189a5789..a902ef0d 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java @@ -47,6 +47,7 @@ import com.cardinalstar.cubicchunks.world.core.ICubicTicketInternal; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; + import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; @@ -229,46 +230,46 @@ public static void onForgeChunkManagerForceChunk(ForgeChunkManager.ForceChunkEve return; } // TODO: properly implement chunk loading -// addForcedCubesHeuristic(event, ticket, (WorldServer) worldInstance); + // addForcedCubesHeuristic(event, ticket, (WorldServer) worldInstance); } -// private static void addForcedCubesHeuristic(ForgeChunkManager.ForceChunkEvent event, -// ForgeChunkManager.Ticket ticket, WorldServer worldInstance) { -// IntSet yCoords = ((ICubicTicketInternal) ticket).getAllForcedChunkCubes() -// .get(event.location); -// if (yCoords != null && !yCoords.isEmpty()) { -// yCoords.forEach( -// cubeY -> ((ICubicWorldInternal) ticket.world) -// .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos) -// .getTickets() -// .add((ITicket) ticket)); -// return; -// } -// WorldServer world = worldInstance; -// CubicPlayerManager cubeMap = (CubicPlayerManager) world.getPlayerManager(); -// ColumnWatcher columnWatcher = cubeMap.get; -// -// if (columnWatcher == null) { -// ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, new IntArraySet()); -// return; // TODO: some different heuristic? -// } -// List players = columnWatcher.getWatchingPlayers(); -// int verticalViewDistance = CubicChunksConfig.verticalCubeLoadDistance; -// if (yCoords == null) { -// yCoords = new IntArraySet(players.size() * verticalViewDistance * 3); -// } -// for (EntityPlayerMP player : players) { -// for (int dy = -verticalViewDistance; dy <= verticalViewDistance; dy++) { -// int cubeY = Coords.getCubeYForEntity(player) + dy; -// Cube cube = (Cube) ((ICubicWorld) world) -// .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos); -// cube.getTickets() -// .add((ITicket) ticket); -// yCoords.add(cubeY); -// } -// } -// ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, yCoords); -// } + // private static void addForcedCubesHeuristic(ForgeChunkManager.ForceChunkEvent event, + // ForgeChunkManager.Ticket ticket, WorldServer worldInstance) { + // IntSet yCoords = ((ICubicTicketInternal) ticket).getAllForcedChunkCubes() + // .get(event.location); + // if (yCoords != null && !yCoords.isEmpty()) { + // yCoords.forEach( + // cubeY -> ((ICubicWorldInternal) ticket.world) + // .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos) + // .getTickets() + // .add((ITicket) ticket)); + // return; + // } + // WorldServer world = worldInstance; + // CubicPlayerManager cubeMap = (CubicPlayerManager) world.getPlayerManager(); + // ColumnWatcher columnWatcher = cubeMap.get; + // + // if (columnWatcher == null) { + // ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, new IntArraySet()); + // return; // TODO: some different heuristic? + // } + // List players = columnWatcher.getWatchingPlayers(); + // int verticalViewDistance = CubicChunksConfig.verticalCubeLoadDistance; + // if (yCoords == null) { + // yCoords = new IntArraySet(players.size() * verticalViewDistance * 3); + // } + // for (EntityPlayerMP player : players) { + // for (int dy = -verticalViewDistance; dy <= verticalViewDistance; dy++) { + // int cubeY = Coords.getCubeYForEntity(player) + dy; + // Cube cube = (Cube) ((ICubicWorld) world) + // .getCubeFromCubeCoords(event.location.chunkXPos, cubeY, event.location.chunkZPos); + // cube.getTickets() + // .add((ITicket) ticket); + // yCoords.add(cubeY); + // } + // } + // ((ICubicTicketInternal) ticket).setForcedChunkCubes(event.location, yCoords); + // } @SubscribeEvent public static void onForgeChunkManagerUnforceChunk(ForgeChunkManager.UnforceChunkEvent event) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java b/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java index d06c8d23..3a0359e5 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/core/ServerHeightMap.java @@ -31,6 +31,7 @@ import com.cardinalstar.cubicchunks.network.CCPacketBuffer; import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.world.cube.Cube; + import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java index fe33710f..28da0b69 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -80,6 +80,7 @@ import com.cardinalstar.cubicchunks.world.core.ICubicTicketInternal; import com.cardinalstar.cubicchunks.world.cube.blockview.IBlockView; import com.gtnewhorizon.gtnhlib.blockpos.BlockPos; + import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; /** @@ -484,7 +485,8 @@ public void tickCubeServer(BooleanSupplier tryToTickFaster, Random rand) { BiomeGenBase biome = biomes3d == null ? null : biomes3d.get(biomeX, biomeY, biomeZ); if (biome == null) { - biome = this.getColumn().getBiomeGenForWorldCoords(biomeX, biomeY, world.provider.worldChunkMgr); + biome = this.getColumn() + .getBiomeGenForWorldCoords(biomeX, biomeY, world.provider.worldChunkMgr); } return biome; @@ -883,8 +885,7 @@ public EnumSet getForceLoadStatus() { if (this.tickets.anyMatch(t -> t instanceof ICubicTicketInternal)) { forcedLoadReasons.add(ForcedLoadReason.MOD_TICKET); } - if (this.tickets.anyMatch( - t -> !(t instanceof SpawnCubes) && !(t instanceof ICubicTicketInternal))) { + if (this.tickets.anyMatch(t -> !(t instanceof SpawnCubes) && !(t instanceof ICubicTicketInternal))) { forcedLoadReasons.add(ForcedLoadReason.OTHER); } return forcedLoadReasons; @@ -894,7 +895,7 @@ public EnumSet getForceLoadStatus() { @Override public T getMeta(MetaKey key) { - //noinspection unchecked + // noinspection unchecked return (T) meta.get(key); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java index 575f6530..307839ed 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java @@ -18,6 +18,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalCause; import com.gtnewhorizon.gtnhlib.hash.Fnv1a64; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java index f2e8f578..cf2971f4 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -12,6 +12,7 @@ import com.cardinalstar.cubicchunks.util.Coords; import com.cardinalstar.cubicchunks.util.ObjectPooler; import com.github.bsideup.jabel.Desugar; + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements PrecalculableNoise { @@ -29,11 +30,11 @@ public class PrecalcedVanillaOctaves extends NoiseGeneratorOctaves implements Pr private final Object paramLock = new Object(); -// private final Cache cache = CacheBuilder.newBuilder() -// .expireAfterWrite(5, TimeUnit.MINUTES) -// .maximumSize(8192) -// .removalListener(notification -> releaseData((NoiseData) notification.getValue())) -// .build(); + // private final Cache cache = CacheBuilder.newBuilder() + // .expireAfterWrite(5, TimeUnit.MINUTES) + // .maximumSize(8192) + // .removalListener(notification -> releaseData((NoiseData) notification.getValue())) + // .build(); private final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap<>(); @@ -82,7 +83,12 @@ public double[] generateNoiseOctaves(double[] data, int blockX, int blockY, int if ((now - lastMessage) > 5e9) { lastMessage = now; - CubicChunks.LOGGER.info("Hits: {} Misses: {} Pres: {} Per call: {}ms", hits, misses2, pres.getAndSet(0), (elapsed / (double)misses2 / 1e6)); + CubicChunks.LOGGER.info( + "Hits: {} Misses: {} Pres: {} Per call: {}ms", + hits, + misses2, + pres.getAndSet(0), + (elapsed / (double) misses2 / 1e6)); hits = 0; misses2 = 0; elapsed = 0; @@ -212,17 +218,7 @@ public NoiseData run() { data.yscale = this.yscale; data.zscale = this.zscale; - base.generateNoiseOctaves( - data.data, - key.x, - key.y, - key.z, - xspan, - yspan, - zspan, - xscale, - yscale, - zscale); + base.generateNoiseOctaves(data.data, key.x, key.y, key.z, xspan, yspan, zspan, xscale, yscale, zscale); return data; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index 49a9a7c9..5c3b3020 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -73,6 +73,7 @@ import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.gtnhlib.util.data.BlockMeta; import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; + import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; @@ -278,23 +279,23 @@ public GenerationResult provideCube(@Nullable Chunk chunk, int cubeX, int IBlockView chunkBlocks = data.right(); if (chunk != null) { - CubicChunks.LOGGER.error("Needed to regenerate a cube within the vanilla chunk for a chunk that already exists: something is fucky ({},{},{})", cubeX, cubeY, cubeZ, new Exception()); + CubicChunks.LOGGER.error( + "Needed to regenerate a cube within the vanilla chunk for a chunk that already exists: something is fucky ({},{},{})", + cubeX, + cubeY, + cubeZ, + new Exception()); } else { chunk = data.left(); generatedColumns.add(chunk); } // Ceiling div by 16 - int heightCubes = (chunkBlocks - .getBounds() + int heightCubes = (chunkBlocks.getBounds() .getSizeY() + 15) >> 4; for (int y = 0; y < heightCubes; y++) { - Cube c = new Cube( - chunk, - y, - chunkBlocks - .subView(Box.horizontalChunkSlice(y << 4, 16))); + Cube c = new Cube(chunk, y, chunkBlocks.subView(Box.horizontalChunkSlice(y << 4, 16))); try { decorator.generate(world, c); @@ -478,8 +479,8 @@ public void populate(Cube cube) { new Vector3i(1, 1, 0), new Vector3i(0, 0, 1), new Vector3i(1, 0, 1), new Vector3i(0, 1, 1), new Vector3i(1, 1, 1), }; -// private static final short[] CUBE_FLAGS = { Cube.POP_100, Cube.POP_010, Cube.POP_110, Cube.POP_001, Cube.POP_101, -// Cube.POP_011, Cube.POP_111, }; + // private static final short[] CUBE_FLAGS = { Cube.POP_100, Cube.POP_010, Cube.POP_110, Cube.POP_001, Cube.POP_101, + // Cube.POP_011, Cube.POP_111, }; private Box getCubesToGenerate(int x, int y, int z) { if (y >= 0 && y < 16) { From 7f4a325cc5ec9e5ac041e12840e9f9d6f795652f Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 28 Nov 2025 13:11:05 -0500 Subject: [PATCH 13/22] Fix EBS Y Level --- src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java index 28da0b69..d1b56832 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -595,7 +595,7 @@ public ExtendedBlockStorage setStorage(@Nullable ExtendedBlockStorage ebs) { public ExtendedBlockStorage getOrCreateStorage() { if (this.storage == null) { - setStorage(new ExtendedBlockStorage(getY(), !column.worldObj.provider.hasNoSky)); + setStorage(new ExtendedBlockStorage(cubeToMinBlock(getY()), !column.worldObj.provider.hasNoSky)); } return storage; From a902c30c1c441126d7b8f7ee89a8f7f00d6f84ac Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 28 Nov 2025 13:11:15 -0500 Subject: [PATCH 14/22] Checkstyle fixes --- .../com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java index 5615abc8..640bc94c 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -1,6 +1,5 @@ package com.cardinalstar.cubicchunks.util; -import java.awt.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; From c11acb1fb20a01fde9d738b049c035c8c5364976 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 28 Nov 2025 13:17:44 -0500 Subject: [PATCH 15/22] fix dep versions --- dependencies.gradle | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 46261a5d..663dbedc 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,15 +34,12 @@ * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - implementation("com.github.GTNewHorizons:GTNHLib:99.99.99:dev") - implementation("com.github.GTNewHorizons:RegionLib:99.99.99:dev") - devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.39-GTNH:dev") + implementation("com.github.GTNewHorizons:GTNHLib:0.8.15:dev") + implementation("com.github.GTNewHorizons:RegionLib:v0.1.0-GTNH:dev") + devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.40-GTNH:dev") - compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.7.0:dev") - compileOnly("com.falsepattern:endlessids-mc1.7.10:1.6.14:dev") - - // implementation("com.falsepattern:chunkapi-mc1.7.10:0.7.0-1-g67006b5-dirty:dev") - // implementation("com.falsepattern:endlessids-mc1.7.10:1.6.14-dirty:dev") + devOnlyNonPublishable("com.falsepattern:chunkapi-mc1.7.10:0.7.0-1-g67006b5-dirty:dev") + devOnlyNonPublishable("com.falsepattern:endlessids-mc1.7.10:1.6.14-dirty:dev") compileOnly("org.jetbrains:annotations:26.0.2") @@ -78,10 +75,10 @@ dependencies { // devOnlyNonPublishable(rfg.deobf('curse.maven:witchery-69673:2234410')) // devOnlyNonPublishable("com.github.GTNewHorizons:DuraDisplay:1.4.0:dev") -// devOnlyNonPublishable('com.github.GTNewHorizons:EnderIO:2.10.3:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:EnderIO:2.10.5:dev') // devOnlyNonPublishable('com.github.GTNewHorizons:MatterManipulator:0.1.3-GTNH:dev') -// devOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.122:dev") { transitive=false } +// devOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.127:dev") { transitive=false } // devOnlyNonPublishable("com.github.GTNewHorizons:Galacticraft:3.4.7-GTNH:dev") { // exclude group: "com.github.GTNewHorizons", module: "GT5-Unofficial" @@ -90,7 +87,7 @@ dependencies { // devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev') // devOnlyNonPublishable rfg.deobf("curse.maven:extra-utilities-225561:2264384") -// devOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.7-GTNH:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.8-GTNH:dev") // // devOnlyNonPublishable("com.github.GTNewHorizons:ForestryMC:4.11.2:dev") // devOnlyNonPublishable('com.github.GTNewHorizons:neiaddons:1.17.0:dev') @@ -101,8 +98,8 @@ dependencies { // // devOnlyNonPublishable("com.github.GTNewHorizons:StructureLib:1.4.24:dev") // devOnlyNonPublishable("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev") -// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI:1.3.0:dev") -// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI2:2.3.15-1.7.10:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI:1.3.1:dev") +// devOnlyNonPublishable("com.github.GTNewHorizons:ModularUI2:2.3.18-1.7.10:dev") // devOnlyNonPublishable("com.github.GTNewHorizons:waila:1.9.15:dev") // devOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-749-GTNH:dev") // devOnlyNonPublishable("com.github.GTNewHorizons:AE2FluidCraft-Rework:1.5.26-gtnh:dev") From ba791566dcb5a7d13dd88079cce0b947ddb2fd7a Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 28 Nov 2025 13:19:22 -0500 Subject: [PATCH 16/22] :clueless: --- dependencies.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 663dbedc..5ae13ee0 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -38,8 +38,11 @@ dependencies { implementation("com.github.GTNewHorizons:RegionLib:v0.1.0-GTNH:dev") devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.40-GTNH:dev") - devOnlyNonPublishable("com.falsepattern:chunkapi-mc1.7.10:0.7.0-1-g67006b5-dirty:dev") - devOnlyNonPublishable("com.falsepattern:endlessids-mc1.7.10:1.6.14-dirty:dev") + compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.7.0:dev") + compileOnly("com.falsepattern:endlessids-mc1.7.10:1.6.14:dev") + +// runtimeOnlyNonPublishable("com.falsepattern:chunkapi-mc1.7.10:0.7.0-1-g67006b5-dirty:dev") +// implementation("com.falsepattern:endlessids-mc1.7.10:1.6.14-dirty:dev") compileOnly("org.jetbrains:annotations:26.0.2") From cf56acac9ad9823cd6c80c1811172232c7f76157 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sun, 11 Jan 2026 13:14:06 -0500 Subject: [PATCH 17/22] Update src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java Co-authored-by: Ryan Nasers <42074409+Cardinalstars@users.noreply.github.com> --- src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java index 190f5c44..3f1ffea0 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java @@ -28,7 +28,7 @@ public static ExtendedBlockStorage getBlockStorage(Chunk chunk, int yLevel) { return cube.getStorage(); } - /// Gets a loaded cube. Does not load or generate the cube. + /// Gets a loaded cube. Does not load or generate the cube. Will return null if the cube is not loaded. @Nullable public static ICube getLoadedCube(World world, int cubeX, int cubeY, int cubeZ) { return ((ICubicWorld) world).getCubeCache() From 686b5af8dd60e7be8909907ec7ccd44e64a7e4b5 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sun, 11 Jan 2026 14:16:51 -0500 Subject: [PATCH 18/22] Update dependency graphs --- .../api/registry/IDependencyGraph.java | 40 ++++++++ .../api/worldgen/DependencyRegistry.java | 16 ---- .../api/worldgen/WorldgenRegistry.java | 5 +- .../impl/StandardDependencyRegistry.java | 56 ------------ .../worldgen/impl/StandardWorldDecorator.java | 11 ++- .../cubicchunks/util/DependencyGraph.java | 91 +++++++++++++------ .../world/worldgen/WorldGenerators.java | 36 ++++---- 7 files changed, 130 insertions(+), 125 deletions(-) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/registry/IDependencyGraph.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/registry/IDependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IDependencyGraph.java new file mode 100644 index 00000000..c80cca10 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IDependencyGraph.java @@ -0,0 +1,40 @@ +package com.cardinalstar.cubicchunks.api.registry; + +/// A dependency graph is something that stores a list of named objects. Each object has a list of dependencies, and +/// these dependencies determine the order that the contained objects will be processed in. +/// This is typically used to control the ordering of third party integrations. If one integration needs to run after or +/// before another, it will add a `before:xyz` or `after:xyz` dependency on the other integration. +/// Objects and dependencies can be added or removed at any point, but this is discouraged because it makes debugging +/// very difficult. +/// Circular dependencies between objects will cause a runtime error and must be avoided. +public interface IDependencyGraph { + + /// The current object must run after another one. These dependencies can be made optional by adding a question mark + /// suffix: `requires:foo?`. An optional dependency will not throw an error if the dependent object is missing. + String REQUIRES = "requires:"; + /// The current object will always run after another one. This is always optional. + String AFTER = "after:"; + /// The opposite of [#REQUIRES]. Ths current object will run before another one. As with [#REQUIRES], this can be + /// made optional by adding a question mark suffix: `required-by:bar?`. + String REQUIRED_BY = "required-by:"; + /// The opposite of [#AFTER]. The current object will always run before another one. As with [#AFTER], this is + /// always optional. + String BEFORE = "before:"; + + /// Adds a named object with one or more dependency specifications (see above) + void addObject(String name, T value, String... deps); + + /// Adds a dependency from an object to another one. `object` will run after `dependsOn`. If this overwrites an + /// existing dependency, the given `optional` parameter overwrites the existing optional-ness. + void addDependency(String object, String dependsOn, boolean optional); + + /// Removes a dependency. Opposite of [#addDependency(String, String, boolean)]. + /// @return True when a dependency was removed. + boolean removeDependency(String object, String dependsOn); + + /// Adds a target. This acts like a systemd target - it can be thought of as a 'goal' instead of a discrete step. + /// As an example, if an integration module needs the system to be in a specific state before it runs, it can + /// depend on a target. Any other objects that affect the target can add a `before:` dependency, which adds a + /// transient dependency between the first and second objects via the target. + void addTarget(String targetName, String... deps); +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java deleted file mode 100644 index 6589df1c..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/DependencyRegistry.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cardinalstar.cubicchunks.api.worldgen; - -import java.util.List; - -public interface DependencyRegistry { - - void register(String name, T value, String... deps); - - void registerTarget(String name, String... deps); - - void addDependency(String from, String to); - - void removeDependency(String from, String to); - - List sorted(); -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java index b9661852..e5c4dd31 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java @@ -1,5 +1,6 @@ package com.cardinalstar.cubicchunks.api.worldgen; +import com.cardinalstar.cubicchunks.api.registry.IDependencyGraph; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; @@ -7,8 +8,8 @@ public interface WorldgenRegistry { /// Terrain generators create the general shape of the terrain and cannot interact with other cubes. This includes /// any noise-based generation that doesn't require information from other cubes such as modern caves. - DependencyRegistry terrain(); + IDependencyGraph terrain(); /// Populators finalize cubes and put the 'window dressing' on the world - ores, trees, etc. - DependencyRegistry population(); + IDependencyGraph population(); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java deleted file mode 100644 index fd8e7909..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardDependencyRegistry.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.cardinalstar.cubicchunks.api.worldgen.impl; - -import java.util.List; - -import com.cardinalstar.cubicchunks.api.worldgen.DependencyRegistry; -import com.cardinalstar.cubicchunks.util.DependencyGraph; -import com.google.common.base.Preconditions; - -public class StandardDependencyRegistry implements DependencyRegistry { - - private final DependencyGraph graph = new DependencyGraph<>(); - - @Override - public void register(String name, T value, String... deps) { - Preconditions.checkNotNull(name); - Preconditions.checkNotNull(value); - - graph.addObject(name, value); - - for (String dep : deps) { - graph.addUnparsedDependency(name, dep); - } - } - - @Override - public void registerTarget(String name, String... deps) { - Preconditions.checkNotNull(name); - - graph.addTarget(name); - - for (String dep : deps) { - graph.addUnparsedDependency(name, dep); - } - } - - @Override - public void addDependency(String from, String to) { - Preconditions.checkNotNull(from); - Preconditions.checkNotNull(to); - - graph.addDependency(from, to); - } - - @Override - public void removeDependency(String from, String to) { - Preconditions.checkNotNull(from); - Preconditions.checkNotNull(to); - - graph.removeDependency(from, to); - } - - @Override - public List sorted() { - return graph.sorted(); - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java index b5e5178f..2826a411 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java @@ -2,27 +2,28 @@ import net.minecraft.world.World; -import com.cardinalstar.cubicchunks.api.worldgen.DependencyRegistry; +import com.cardinalstar.cubicchunks.api.registry.IDependencyGraph; import com.cardinalstar.cubicchunks.api.worldgen.WorldgenRegistry; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubeGenerator; import com.cardinalstar.cubicchunks.api.worldgen.decoration.ICubePopulator; import com.cardinalstar.cubicchunks.api.worldgen.decoration.IWorldDecorator; import com.cardinalstar.cubicchunks.util.CubePos; +import com.cardinalstar.cubicchunks.util.DependencyGraph; import com.cardinalstar.cubicchunks.world.cube.Cube; public class StandardWorldDecorator implements WorldgenRegistry, IWorldDecorator { - private final StandardDependencyRegistry terrain = new StandardDependencyRegistry<>(); + private final DependencyGraph terrain = new DependencyGraph<>(); - private final StandardDependencyRegistry population = new StandardDependencyRegistry<>(); + private final DependencyGraph population = new DependencyGraph<>(); @Override - public DependencyRegistry terrain() { + public IDependencyGraph terrain() { return terrain; } @Override - public DependencyRegistry population() { + public IDependencyGraph population() { return population; } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java index 51952e0d..be9e2a89 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -4,29 +4,28 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import org.jetbrains.annotations.Nullable; + +import com.cardinalstar.cubicchunks.api.registry.IDependencyGraph; +import com.github.bsideup.jabel.Desugar; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -public class DependencyGraph { - - private static class DepInfo { +public class DependencyGraph implements IDependencyGraph { - public final String dependency; - public final boolean optional; - - public DepInfo(String dependency, boolean optional) { - this.dependency = dependency; - this.optional = optional; - } + @Desugar + private record DepInfo(String dependency, boolean optional) { @Override - public final boolean equals(Object o) { + public boolean equals(Object o) { if (!(o instanceof DepInfo depInfo)) return false; return Objects.equals(dependency, depInfo.dependency); @@ -38,7 +37,8 @@ public int hashCode() { } } - private final Object2ObjectOpenHashMap objects = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectOpenHashMap> objects = new Object2ObjectOpenHashMap<>(); + private final ObjectOpenHashSet targets = new ObjectOpenHashSet<>(); private final Multimap dependencies = MultimapBuilder.hashKeys() .hashSetValues() @@ -46,16 +46,22 @@ public int hashCode() { private List cachedSorted; - public void addObject(String name, T value) { + public DependencyGraph() { + objects.defaultReturnValue(null); + } + + @Override + public void addObject(String name, T value, String... deps) { Objects.requireNonNull(name, "name must not be null"); Objects.requireNonNull(value, "value must not be null"); - objects.put(name, value); + objects.put(name, Optional.of(value)); cachedSorted = null; - } - public static final String REQUIRES = "requires:"; - public static final String REQUIRED_BY = "required-by:"; + for (String dep : deps) { + addUnparsedDependency(name, dep); + } + } public void addUnparsedDependency(String object, String dep) { Objects.requireNonNull(object, "object must not be null"); @@ -75,33 +81,57 @@ public void addUnparsedDependency(String object, String dep) { dep = dep.substring(REQUIRED_BY.length()) .trim(); dependencies.put(dep, new DepInfo(object, optional)); + } else if (dep.startsWith(AFTER)) { + dep = dep.substring(AFTER.length()) + .trim(); + dependencies.put(object, new DepInfo(dep, true)); + } else if (dep.startsWith(BEFORE)) { + dep = dep.substring(BEFORE.length()) + .trim(); + dependencies.put(dep, new DepInfo(object, true)); } else { - dependencies.put(object, new DepInfo(dep, optional)); + throw new IllegalArgumentException("Invalid dependency specification for object '" + object + "': '" + dep + "'"); } cachedSorted = null; } - public void addDependency(String object, String dependsOn) { + @Override + public void addDependency(String object, String dependsOn, boolean optional) { Objects.requireNonNull(object, "object must not be null"); Objects.requireNonNull(dependsOn, "dependsOn must not be null"); - dependencies.put(object, new DepInfo(dependsOn, false)); + dependencies.put(object, new DepInfo(dependsOn, optional)); cachedSorted = null; } - public void removeDependency(String object, String dependsOn) { + @Override + public boolean removeDependency(String object, String dependsOn) { Objects.requireNonNull(object, "object must not be null"); Objects.requireNonNull(dependsOn, "dependsOn must not be null"); - dependencies.remove(object, new DepInfo(dependsOn, false)); + boolean successful = dependencies.remove(object, new DepInfo(dependsOn, false)); cachedSorted = null; + + return successful; } - public void addTarget(String targetName) { + @Override + public void addTarget(String targetName, String... deps) { Objects.requireNonNull(targetName, "targetName must not be null"); - objects.put(targetName, null); + objects.put(targetName, Optional.empty()); + targets.add(targetName); + + for (String dep : deps) { + addUnparsedDependency(targetName, dep); + } + } + + /// Not made public because there is no well-defined initialization timing. + @Nullable + public Optional get(String name) { + return objects.get(name); } public List sorted() { @@ -133,12 +163,13 @@ public List sorted() { added.add(curr); - T value = objects.get(curr); + Optional value = objects.get(curr); - // Only targets are null - if (value != null) { - out.add(value); + if (value == null) { + throw new IllegalStateException("Missing value for key: " + curr); } + + value.ifPresent(out::add); } } @@ -152,10 +183,10 @@ private void preventCyclicDeps(String node, boolean optional, Set path) throw new IllegalStateException( node + " has a cyclic dependency with itself. The path is: " + path.stream() - .reduce("", (s, s2) -> s + ", " + s2)); + .reduce("", (s, s2) -> s + ", " + s2)); } - if (!optional && !objects.containsKey(node)) { + if (!optional && !targets.contains(node) && !objects.containsKey(node)) { throw new IllegalStateException( node + " is present in the dependency graph but does not have a matching object"); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java index 2b31c698..f66abea8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -8,11 +8,13 @@ import com.cardinalstar.cubicchunks.util.DoubleInterval; import com.cardinalstar.cubicchunks.util.Mods; +import com.cardinalstar.cubicchunks.world.worldgen.caves.CaveBiomePopulator; +import com.cardinalstar.cubicchunks.world.worldgen.caves.NoodleCaveGenerator; +import com.cardinalstar.cubicchunks.world.worldgen.caves.SpaghettiCaveGenerator; import com.cardinalstar.cubicchunks.world.worldgen.compat.DeepslateCubePopulator; import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; - import cpw.mods.fml.common.Optional; public class WorldGenerators { @@ -21,26 +23,23 @@ public class WorldGenerators { private static final LazyBlock LAVA_STILL = new LazyBlock(Mods.Minecraft, () -> Blocks.lava); public static void init() { - initVanilla(); + initVanillaTerrain(); + initVanillaPopulation(); if (Mods.EtFuturumRequiem.isModLoaded()) { - initEFR(); + initEFRPopulation(); } } - private static void initVanilla() { - // CUBIC_VANILLA.terrain().register( - // "noodle-caves", - // new NoodleCaveGenerator(), - // "required-by:caves-all"); - // - // CUBIC_VANILLA.terrain().register( - // "spaghetti-caves", - // new SpaghettiCaveGenerator(), - // "required-by:caves-all"); + private static void initVanillaTerrain() { + CUBIC_VANILLA.terrain() + .addObject("noodle-caves", new NoodleCaveGenerator(), "required-by:caves-all"); + + CUBIC_VANILLA.terrain() + .addObject("spaghetti-caves", new SpaghettiCaveGenerator(), "required-by:caves-all"); CUBIC_VANILLA.terrain() - .registerTarget("caves-all"); + .addTarget("caves-all"); // TODO: block carver // TODO: pillar caves @@ -55,10 +54,15 @@ private static void initVanilla() { // new MapGenCaveFluids(LAVA_STILL)); } + private static void initVanillaPopulation() { + CUBIC_VANILLA.population() + .addObject("biomes", new CaveBiomePopulator()); + } + @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) - private static void initEFR() { + private static void initEFRPopulation() { CUBIC_VANILLA.population() - .register("low-deepslate", new DeepslateCubePopulator()); + .addObject("low-deepslate", new DeepslateCubePopulator(), "requires:biomes"); } private static final double CHOOSER_SCALE = 0.01; From 25b07042914c83dbfc2fc99fbea7b2471c213aac Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sun, 11 Jan 2026 14:28:24 -0500 Subject: [PATCH 19/22] oops --- .../world/worldgen/WorldGenerators.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java index f66abea8..6cf0828f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -8,9 +8,9 @@ import com.cardinalstar.cubicchunks.util.DoubleInterval; import com.cardinalstar.cubicchunks.util.Mods; -import com.cardinalstar.cubicchunks.world.worldgen.caves.CaveBiomePopulator; -import com.cardinalstar.cubicchunks.world.worldgen.caves.NoodleCaveGenerator; -import com.cardinalstar.cubicchunks.world.worldgen.caves.SpaghettiCaveGenerator; +// import com.cardinalstar.cubicchunks.world.worldgen.caves.CaveBiomePopulator; +// import com.cardinalstar.cubicchunks.world.worldgen.caves.NoodleCaveGenerator; +// import com.cardinalstar.cubicchunks.world.worldgen.caves.SpaghettiCaveGenerator; import com.cardinalstar.cubicchunks.world.worldgen.compat.DeepslateCubePopulator; import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; @@ -32,11 +32,11 @@ public static void init() { } private static void initVanillaTerrain() { - CUBIC_VANILLA.terrain() - .addObject("noodle-caves", new NoodleCaveGenerator(), "required-by:caves-all"); + // CUBIC_VANILLA.terrain() + // .addObject("noodle-caves", new NoodleCaveGenerator(), "required-by:caves-all"); - CUBIC_VANILLA.terrain() - .addObject("spaghetti-caves", new SpaghettiCaveGenerator(), "required-by:caves-all"); + // CUBIC_VANILLA.terrain() + // .addObject("spaghetti-caves", new SpaghettiCaveGenerator(), "required-by:caves-all"); CUBIC_VANILLA.terrain() .addTarget("caves-all"); @@ -55,8 +55,8 @@ private static void initVanillaTerrain() { } private static void initVanillaPopulation() { - CUBIC_VANILLA.population() - .addObject("biomes", new CaveBiomePopulator()); + // CUBIC_VANILLA.population() + // .addObject("biomes", new CaveBiomePopulator()); } @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) From 44b764b7d6d94de50334e646a19f8577cd991af7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 14:45:34 -0500 Subject: [PATCH 20/22] spotlessApply (#40) Co-authored-by: GitHub GTNH Actions <> --- .../cardinalstar/cubicchunks/util/DependencyGraph.java | 5 +++-- .../cubicchunks/world/worldgen/WorldGenerators.java | 10 ++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java index be9e2a89..4d3f4b51 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -90,7 +90,8 @@ public void addUnparsedDependency(String object, String dep) { .trim(); dependencies.put(dep, new DepInfo(object, true)); } else { - throw new IllegalArgumentException("Invalid dependency specification for object '" + object + "': '" + dep + "'"); + throw new IllegalArgumentException( + "Invalid dependency specification for object '" + object + "': '" + dep + "'"); } cachedSorted = null; @@ -183,7 +184,7 @@ private void preventCyclicDeps(String node, boolean optional, Set path) throw new IllegalStateException( node + " has a cyclic dependency with itself. The path is: " + path.stream() - .reduce("", (s, s2) -> s + ", " + s2)); + .reduce("", (s, s2) -> s + ", " + s2)); } if (!optional && !targets.contains(node) && !objects.containsKey(node)) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java index 6cf0828f..102187cc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -8,13 +8,11 @@ import com.cardinalstar.cubicchunks.util.DoubleInterval; import com.cardinalstar.cubicchunks.util.Mods; -// import com.cardinalstar.cubicchunks.world.worldgen.caves.CaveBiomePopulator; -// import com.cardinalstar.cubicchunks.world.worldgen.caves.NoodleCaveGenerator; -// import com.cardinalstar.cubicchunks.world.worldgen.caves.SpaghettiCaveGenerator; import com.cardinalstar.cubicchunks.world.worldgen.compat.DeepslateCubePopulator; import com.cardinalstar.cubicchunks.world.worldgen.noise.OctavesSampler; import com.cardinalstar.cubicchunks.world.worldgen.noise.ScaledNoise; import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; + import cpw.mods.fml.common.Optional; public class WorldGenerators { @@ -33,10 +31,10 @@ public static void init() { private static void initVanillaTerrain() { // CUBIC_VANILLA.terrain() - // .addObject("noodle-caves", new NoodleCaveGenerator(), "required-by:caves-all"); + // .addObject("noodle-caves", new NoodleCaveGenerator(), "required-by:caves-all"); // CUBIC_VANILLA.terrain() - // .addObject("spaghetti-caves", new SpaghettiCaveGenerator(), "required-by:caves-all"); + // .addObject("spaghetti-caves", new SpaghettiCaveGenerator(), "required-by:caves-all"); CUBIC_VANILLA.terrain() .addTarget("caves-all"); @@ -56,7 +54,7 @@ private static void initVanillaTerrain() { private static void initVanillaPopulation() { // CUBIC_VANILLA.population() - // .addObject("biomes", new CaveBiomePopulator()); + // .addObject("biomes", new CaveBiomePopulator()); } @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) From b6ae1aa8d6c642aab41cc144cf7cd03317cfab6a Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Fri, 16 Jan 2026 12:28:29 -0500 Subject: [PATCH 21/22] Update src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java Co-authored-by: Ryan Nasers <42074409+Cardinalstars@users.noreply.github.com> --- .../com/cardinalstar/cubicchunks/server/CubicPlayerManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java index a69898ab..db368948 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -93,7 +93,7 @@ public class CubicPlayerManager extends PlayerManager implements CubeLoaderCallback { /** - * Mapping if entityId to PlayerCubeMap.PlayerWrapper objects. + * Mapping of entityId to PlayerCubeMap.PlayerWrapper objects. */ private final Int2ObjectOpenHashMap players = new Int2ObjectOpenHashMap<>(); From db3710b280d03c77e5ef11b8dd77e58b4c600bb5 Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Sat, 31 Jan 2026 15:21:39 -0500 Subject: [PATCH 22/22] Review changes + biome fixes --- .../cubicchunks/server/chunkio/CubeIO.java | 2 +- .../server/chunkio/CubeLoaderCallback.java | 4 +- .../server/chunkio/CubeLoaderServer.java | 4 +- .../cubicchunks/util/biome3d/BiomeArray.java | 8 ++ .../util/biome3d/DynamicBiomeArray.java | 106 +++++------------ .../biome3d/InvalidBiomeDataException.java | 8 -- .../util/biome3d/NaiveCompression.java | 75 ++++++++++++ .../util/biome3d/PaletteFullError.java | 3 + .../util/biome3d/PalettizedBiomeArray.java | 63 +++++----- .../cubicchunks/util/biome3d/BiomeTests.java | 112 ++++++++++++++++++ 10 files changed, 265 insertions(+), 120 deletions(-) delete mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/biome3d/NaiveCompression.java create mode 100644 src/test/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeTests.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java index 70c52a77..4642da35 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -357,7 +357,7 @@ public void close() throws IOException { @Override public void preloadColumn(ChunkCoordIntPair pos) { TaskPool.submit(columnLoadExecutor, pos, tag -> { - if (tag == null) { + if (!tag.isPresent()) { if (preloadFailures != null) preloadFailures.onColumnPreloadFailed(pos); } else { synchronized (columnCache) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java index ea0a7c16..5ae54fe8 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderCallback.java @@ -24,8 +24,8 @@ default void onCubeLoaded(Cube cube) { } /** - * This is called when a cube is generated. It is called when a cube is loaded, then generated further, and when the - * cube is initially generated from nothing. + * This is called when a cube is generated. It is called when a cube's init level increases - that is, when an + * already loaded cube is generated further, or when a cube is newly generated. */ default void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { diff --git a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java index d1e33b08..79d55dd2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -164,9 +164,7 @@ public Cube getLoadedCube(int x, int y, int z) { public boolean cubeExists(int x, int y, int z) { if (getLoadedCube(x, y, z) != null) return true; - if (cubeIO.cubeExists(new CubePos(x, y, z))) return true; - - return false; + return cubeIO.cubeExists(new CubePos(x, y, z)); } @Override diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java index dda2f112..4ce79c1b 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java @@ -12,6 +12,14 @@ public interface BiomeArray extends Int2ObjectFunction { boolean isEmpty(); + @Override + void clear(); + + @Override + default int size() { + return 16 * 16 * 16; + } + default BiomeGenBase put(int x, int y, int z, BiomeGenBase value) { return put(AddressTools.getLocalAddress(x, y, z), value); } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java index e5e6daf1..ea29926e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java @@ -3,10 +3,12 @@ import net.minecraft.world.biome.BiomeGenBase; import com.cardinalstar.cubicchunks.network.CCPacketBuffer; +import com.cardinalstar.cubicchunks.util.biome3d.NaiveCompression.NaiveCompressionDataInput; +import com.cardinalstar.cubicchunks.util.biome3d.NaiveCompression.NaiveCompressionDataOutput; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - +/// This is a [BiomeArray] implementation that dynamically switches its backing storage to the most optimal format as +/// needed. By default it will use a [PalettizedBiomeArray], and once its palette is completely full, it will migrate +/// the data to a [ReferenceBiomeArray]. public class DynamicBiomeArray implements BiomeArray { private BiomeArray backing = new PalettizedBiomeArray(); @@ -16,11 +18,6 @@ public boolean isEmpty() { return backing.isEmpty(); } - @Override - public int size() { - return 16 * 16 * 16; - } - @Override public void clear() { backing.clear(); @@ -29,8 +26,10 @@ public void clear() { @Override public BiomeGenBase put(int key, BiomeGenBase value) { try { + // Try to insert the biome value into the storage backing.put(key, value); } catch (PaletteFullError e) { + // If we're using a palette array and its palette is full, migrate the data BiomeArray reference = new ReferenceBiomeArray(); int size = size(); @@ -44,6 +43,7 @@ public BiomeGenBase put(int key, BiomeGenBase value) { backing.put(key, value); } + // Boilerplate for Int2ObjectFunction.put return null; } @@ -52,6 +52,7 @@ public BiomeGenBase get(int key) { return backing.get(key); } + /// Changes the 'default' biome, since we don't want null biomes in these arrays @Override public void defaultReturnValue(BiomeGenBase rv) { backing.defaultReturnValue(rv); @@ -62,86 +63,33 @@ public BiomeGenBase defaultReturnValue() { return backing.defaultReturnValue(); } - private static final byte ADD_PALLETE = 0; - private static final byte DATA = 1; - public void write(CCPacketBuffer buffer) { - Object2IntOpenHashMap palette = new Object2IntOpenHashMap<>(); - - palette.defaultReturnValue(-1); - - int start = buffer.writerIndex(); - - int ops = 0; - - for (int i = 0; i < size(); i++) { - BiomeGenBase curr = get(i); - - int paletteIndex = palette.getInt(curr); - - if (paletteIndex == -1) { - paletteIndex = palette.size(); - palette.put(curr, paletteIndex); + NaiveCompression.compress(new NaiveCompressionDataInput() { - buffer.writeByte(ADD_PALLETE); - buffer.writeVarIntToBuffer(curr.biomeID); - ops++; + @Override + public int size() { + return DynamicBiomeArray.this.size(); } - int i2 = i + 1; - - while (i2 < size() && get(i2) == curr) { - i2++; + @Override + public int get(int index) { + return DynamicBiomeArray.this.get(index).biomeID; } - - int len = i2 - i; - - buffer.writeByte(DATA); - buffer.writeVarIntToBuffer(paletteIndex); - buffer.writeVarIntToBuffer(len); - ops++; - } - - int end = buffer.writerIndex(); - - buffer.writerIndex(start); - buffer.writeInt(ops); - - buffer.writerIndex(end); + }, buffer); } public void read(CCPacketBuffer buffer) { - int ops = buffer.readInt(); - - ObjectArrayList palette = new ObjectArrayList<>(); - - int i = 0; - - for (int op = 0; op < ops; op++) { - byte insn = buffer.readByte(); - switch (insn) { - case ADD_PALLETE -> { - BiomeGenBase biome = BiomeGenBase.getBiome(buffer.readVarIntFromBuffer()); - - palette.add(biome); - } - case DATA -> { - BiomeGenBase biome = palette.get(buffer.readVarIntFromBuffer()); - int len = buffer.readVarIntFromBuffer(); - - for (int k = 0; k < len; k++) { - put(i + k, biome); - } - - i += len; - } - default -> { - throw new InvalidBiomeDataException( - "Unexpected operation " + insn + " at index" + buffer.readerIndex()); - } + NaiveCompression.decompress(buffer, new NaiveCompressionDataOutput() { + + @Override + public int size() { + return DynamicBiomeArray.this.size(); } - } - if (i != size()) throw new InvalidBiomeDataException("Expected " + size() + " biomes indices, got " + i); + @Override + public void set(int index, int value) { + DynamicBiomeArray.this.put(index, BiomeGenBase.getBiome(value)); + } + }); } } diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java deleted file mode 100644 index 467ab099..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/InvalidBiomeDataException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cardinalstar.cubicchunks.util.biome3d; - -public class InvalidBiomeDataException extends RuntimeException { - - public InvalidBiomeDataException(String message) { - super(message); - } -} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/NaiveCompression.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/NaiveCompression.java new file mode 100644 index 00000000..d641ca03 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/NaiveCompression.java @@ -0,0 +1,75 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; + +/// Fast but very simple compression that assumes the data has long stretches of contiguous values. This algorithm does +/// not check if the result is actually smaller so noisy data may take more space than the original data. +/// This works by scanning the data input and squishing lengths of the same values. If the input buffer contains a +/// single repeated value, the resulting buffer will consist of a single [#OP_DATA], followed by a [#OP_DONE]. +public class NaiveCompression { + + /// Some data. Has two parameters: an int for the value, followed by a var int for the length. + private static final byte OP_DATA = 0; + /// Indicates that the stream is done. Has no parameters. + private static final byte OP_DONE = 1; + + public interface NaiveCompressionDataInput { + + int size(); + + int get(int index); + } + + public static void compress(NaiveCompressionDataInput data, CCPacketBuffer buffer) { + int i = 0; + + int size = data.size(); + + while (i < size) { + int curr = data.get(i); + + int i2 = i + 1; + + while (i2 < size && data.get(i2) == curr) { + i2++; + } + + int len = i2 - i; + + buffer.writeByte(OP_DATA); + buffer.writeInt(curr); + buffer.writeVarIntToBuffer(len); + + i = i2; + } + + buffer.writeByte(OP_DONE); + } + + public interface NaiveCompressionDataOutput { + + int size(); + + void set(int index, int value); + } + + public static void decompress(CCPacketBuffer buffer, NaiveCompressionDataOutput data) { + byte op; + int i = 0; + + while ((op = buffer.readByte()) == OP_DATA) { + int value = buffer.readInt(); + int len = buffer.readVarIntFromBuffer(); + + for (int k = 0; k < len; k++) { + data.set(i + k, value); + } + + i += len; + } + + if (op != OP_DONE) throw new IllegalStateException("Expected buffer to end with DONE operation"); + if (i != data.size()) + throw new IllegalStateException("Expected buffer to decompress to " + data.size() + " bytes of data"); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java index 3a94d564..1d855930 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java @@ -1,5 +1,8 @@ package com.cardinalstar.cubicchunks.util.biome3d; +/// This is thrown when a [PalettizedBiomeArray]'s palette is full, which indicates that it must be converted to a +/// [ReferenceBiomeArray]. Exceptions are used here to avoid object allocations in a hot path - it's not ideal but it's +/// the cleanest solution that I can think of. public class PaletteFullError extends RuntimeException { public PaletteFullError() { diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java index 429bbd6e..6d1d5710 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java @@ -8,35 +8,31 @@ public class PalettizedBiomeArray implements BiomeArray { - private int freePaletteIndices = ~0b1; + private final boolean[] usedPaletteIndices = new boolean[16]; private final BiomeGenBase[] palette = new BiomeGenBase[16]; private final Object2IntOpenHashMap paletteReversed = new Object2IntOpenHashMap<>(); - { + public PalettizedBiomeArray() { paletteReversed.defaultReturnValue(-1); paletteReversed.put(null, 0); palette[0] = null; + usedPaletteIndices[0] = true; } - private final byte[] data = new byte[16 * 16 * 16 / 2]; + private final byte[] nibbleArray = new byte[16 * 16 * 16 / 2]; @Override public boolean isEmpty() { - for (byte b : data) { + for (byte b : nibbleArray) { if (b != 0) return false; } return true; } - @Override - public int size() { - return 16 * 16 * 16; - } - @Override public void clear() { - Arrays.fill(data, (byte) 0); + Arrays.fill(nibbleArray, (byte) 0); } @Override @@ -44,12 +40,28 @@ public BiomeGenBase put(int key, BiomeGenBase value) { int paletteIndex = paletteReversed.getInt(value); if (paletteIndex == -1) { - int free = Integer.lowestOneBit(freePaletteIndices); + int free = -1; + + for (int i = 0; i < usedPaletteIndices.length; i++) { + boolean b = usedPaletteIndices[i]; + + if (b) continue; + + free = i; + break; + } if (free >= 16) { cleanPalette(); - free = Integer.lowestOneBit(freePaletteIndices); + for (int i = 0; i < usedPaletteIndices.length; i++) { + boolean b = usedPaletteIndices[i]; + + if (b) continue; + + free = i; + break; + } if (free >= 16) { throw new PaletteFullError(); @@ -59,10 +71,10 @@ public BiomeGenBase put(int key, BiomeGenBase value) { paletteIndex = free; palette[free] = value; paletteReversed.put(value, free); - freePaletteIndices &= ~(0b1 << paletteIndex); + usedPaletteIndices[paletteIndex] = true; } - byte b = data[key >> 1]; + byte b = nibbleArray[key >> 1]; if ((key & 0b1) == 0) { b = (byte) ((b & 0xF0) | paletteIndex); @@ -70,22 +82,18 @@ public BiomeGenBase put(int key, BiomeGenBase value) { b = (byte) ((paletteIndex << 4) | (b & 0xF)); } - data[key >> 1] = b; + nibbleArray[key >> 1] = b; return null; } @Override public BiomeGenBase get(int key) { - byte b = data[key >> 1]; - - if ((key & 0b1) == 0) { - b >>= 4; - } + int index = key >> 1; - b &= 0xF; + int id = (key & 1) == 0 ? (this.nibbleArray[index] & 0xF) : (this.nibbleArray[index] >> 4 & 0xF); - return palette[b]; + return palette[id]; } @Override @@ -103,14 +111,15 @@ public BiomeGenBase defaultReturnValue() { } public void cleanPalette() { - freePaletteIndices = ~0b1; + Arrays.fill(usedPaletteIndices, false); + usedPaletteIndices[0] = true; - for (byte b : data) { + for (byte b : nibbleArray) { int lower = b & 0xF; int upper = (b >> 4) & 0xF; - freePaletteIndices &= ~(0b1 << lower); - freePaletteIndices &= ~(0b1 << upper); + usedPaletteIndices[lower] = true; + usedPaletteIndices[upper] = true; } paletteReversed.clear(); @@ -119,7 +128,7 @@ public void cleanPalette() { paletteReversed.put(palette[0], 0); for (int i = 1; i < 16; i++) { - if ((freePaletteIndices & (0b1 << i)) != 0) { + if (!usedPaletteIndices[i]) { palette[i] = null; } else { if (palette[i] != null) { diff --git a/src/test/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeTests.java b/src/test/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeTests.java new file mode 100644 index 00000000..efd2bd1f --- /dev/null +++ b/src/test/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeTests.java @@ -0,0 +1,112 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +import net.minecraft.world.biome.BiomeGenBase; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.cardinalstar.cubicchunks.network.CCPacketBuffer; +import com.cardinalstar.cubicchunks.util.XSTR; +import com.cardinalstar.cubicchunks.util.biome3d.NaiveCompression.NaiveCompressionDataInput; +import com.cardinalstar.cubicchunks.util.biome3d.NaiveCompression.NaiveCompressionDataOutput; + +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class BiomeTests { + + @Test + public void compression() { + XSTR rng = new XSTR(5); + + IntArrayList input = new IntArrayList(); + input.size(1024); + + for (int i = 0; i < input.size(); i++) { + input.set(i, rng.nextInt()); + } + + IntArrayList output = new IntArrayList(); + output.size(1024); + + CCPacketBuffer buffer = new CCPacketBuffer(Unpooled.buffer()); + + NaiveCompression.compress(new NaiveCompressionDataInput() { + + @Override + public int size() { + return input.size(); + } + + @Override + public int get(int index) { + return input.getInt(index); + } + }, buffer); + + NaiveCompression.decompress(buffer, new NaiveCompressionDataOutput() { + + @Override + public int size() { + return output.size(); + } + + @Override + public void set(int index, int value) { + output.set(index, value); + } + }); + + for (int i = 0; i < input.size(); i++) { + Assertions.assertEquals( + input.getInt(i), + output.getInt(i), + "index " + i + " was " + output.getInt(i) + " but " + input.getInt(i) + " was expected"); + } + } + + @Test + public void dynamicArray() { + XSTR rng = new XSTR(5); + + DynamicBiomeArray array = new DynamicBiomeArray(); + BiomeGenBase[] input = new BiomeGenBase[array.size()]; + + for (int i = 0; i < array.size(); i++) { + BiomeGenBase biome = rng.nextBoolean() ? BiomeGenBase.birchForest : BiomeGenBase.coldBeach; + input[i] = biome; + array.put(i, biome); + } + + for (int i = 0; i < array.size(); i++) { + Assertions.assertEquals( + input[i], + array.get(i), + "array: index " + i + + " was " + + array.get(i).biomeName + + " but " + + input[i].biomeName + + " was expected"); + } + + DynamicBiomeArray array2 = new DynamicBiomeArray(); + + CCPacketBuffer buffer = new CCPacketBuffer(Unpooled.buffer()); + + array.write(buffer); + array2.read(buffer); + + for (int i = 0; i < array.size(); i++) { + Assertions.assertEquals( + array.get(i), + array2.get(i), + "serialization: index " + i + + " was " + + array2.get(i).biomeName + + " but " + + array.get(i).biomeName + + " was expected"); + } + } +}