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 97f97d30..5ae13ee0 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,12 +34,81 @@ * 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") - runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.4-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") - 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") + +// 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") + + 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") + // 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: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.5:dev') +// devOnlyNonPublishable('com.github.GTNewHorizons:MatterManipulator:0.1.3-GTNH:dev') + +// 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" +// } + +// 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.8-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.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") +// 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/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 fdb63109..7ee0aceb 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -3,6 +3,42 @@ repositories { mavenCentral() maven { - setUrl("https://oss.sonatype.org/content/repositories/public/") + 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 3128a6a6..f6d07b8b 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,9 @@ 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.async.TaskPool; import com.cardinalstar.cubicchunks.event.handlers.ClientEventHandler; import com.cardinalstar.cubicchunks.event.handlers.CommonEventHandler; import com.cardinalstar.cubicchunks.mixin.api.ICubicWorldSettings; @@ -48,14 +50,14 @@ 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.type.VanillaCubicWorldType; -import com.cardinalstar.cubicchunks.worldgen.VanillaCompatibilityGenerator; +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.client.FMLClientHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ICrashCallable; import cpw.mods.fml.common.Loader; @@ -65,7 +67,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.eventhandler.SubscribeEvent; import cpw.mods.fml.common.network.NetworkCheckHandler; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.internal.NetworkModHolder; @@ -121,8 +122,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) { @@ -145,8 +149,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 @@ -155,6 +157,8 @@ public String call() throws Exception { Loader.instance() .activeModContainer()); holder.testVanillaAcceptance(); + WorldGenerators.init(); + TaskPool.init(); } @Mod.EventHandler @@ -177,6 +181,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 @@ -196,31 +204,8 @@ 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 @@ -300,10 +285,29 @@ public static void bigWarning(String format, Object... data) { LOGGER.log(Level.WARN, "****************************************"); } - public static boolean hasOptifine() { - return SideUtils.getForSide( - () -> () -> FMLClientHandler.instance() - .hasOptifine(), - () -> () -> false); + 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..b7c6561d 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; @@ -71,12 +71,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.") @@ -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 " @@ -140,10 +144,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 +228,19 @@ 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 +368,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 +376,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/CCAPI.java b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java new file mode 100644 index 00000000..3f1ffea0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/CCAPI.java @@ -0,0 +1,80 @@ +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. 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() + .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 be31abce..aed11f36 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/ICube.java @@ -38,14 +38,16 @@ 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 org.jetbrains.annotations.NotNull; + +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; @ParametersAreNonnullByDefault -public interface ICube extends XYZAddressable { +public interface ICube extends XYZAddressable, MetaContainer { /** * Side length of a cube @@ -216,7 +218,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 +227,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 +248,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(); } @@ -264,35 +266,20 @@ default boolean isInitializedToLevel(CubeLoaderServer.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/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 2686e610..45974897 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java +++ b/src/main/java/com/cardinalstar/cubicchunks/api/XYZMap.java @@ -25,6 +25,8 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import com.cardinalstar.cubicchunks.util.Coords; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; /** @@ -40,28 +42,36 @@ 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)); + 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) { - return items.get(key(x, y, 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)); } public final T get(XYZAddressable xyz) { return get(xyz.getX(), xyz.getY(), xyz.getZ()); } - public final void put(T item) { - items.put(key(item.getX(), item.getY(), item.getZ()), item); + 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); } - 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/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/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/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/registry/IRegistryEntry.java b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java new file mode 100644 index 00000000..c94fe801 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/registry/IRegistryEntry.java @@ -0,0 +1,23 @@ +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 38e4cf21..8a94326c 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; @@ -45,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; } @@ -69,6 +75,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; } @@ -77,8 +95,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) { @@ -241,6 +259,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/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/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..5b05a8e5 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,21 @@ 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; -public abstract class StorageFormatProviderBase implements ICubicChunksRegistryEntry { +import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; - public static final ResourceLocation DEFAULT = new ResourceLocation("cubicchunks", "anvil3d"); - public static CubicChunksRegistry REGISTRY = new CubicChunksRegistry<>();; +public abstract class StorageFormatFactory extends AbstractRegistryEntry { - public ResourceLocation registryName; - public String unlocalizedName; + public static final UniqueIdentifier DEFAULT = new UniqueIdentifier("cubicchunks:anvil3d"); + public static final Registry REGISTRY = new Registry<>(); - @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 1ac3c43f..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/CubeGeneratorsRegistry.java +++ /dev/null @@ -1,210 +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.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.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 { - - /** 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 TreeSet> sortedVanillaGeneratorList = new TreeSet<>(); - - private static final TreeSet sortedPopulatorList = new TreeSet<>(); - - 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); - } - } - - @Desugar - private record PopulatorWrapper(ICubicPopulator populator, int weight) implements Comparable { - - @Override - public int compareTo(PopulatorWrapper 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(ICubeTerrainGenerator generator, - int priority) { - Preconditions.checkNotNull(generator); - sortedVanillaGeneratorList.add(new GeneratorWrapper<>(generator, priority)); - } - - /** - * 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); - } - } - - /** - * 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) - */ - public static void registerVanillaPopulator(ICubicPopulator populator, int weight) { - 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); - } - } - - /** - * 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) - */ - public static void registerPopulator(ICubicPopulator populator, int weight) { - Preconditions.checkNotNull(populator); - sortedPopulatorList.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 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); - } - } - - /** - * 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); - } - - static { - registerVanillaGenerator(new MapGenCavesCubic(), 1); - } - - public static void populateVanillaCubic(World world, ICube cube) { - for (ICubicPopulator populator : customPopulatorsForFlatCubicGenerator) { - populator.generate(world, cube.getCoords()); - } - } - - /** - * 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/GenerationResult.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java new file mode 100644 index 00000000..16de93f6 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/GenerationResult.java @@ -0,0 +1,28 @@ +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..3202ae4c 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(@Nullable 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); @@ -185,28 +105,10 @@ default GeneratorReadyState pollAsyncCubePopulator(int cubeX, int cubeY, int cub * 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 pos find the structure closest to this position - * @param findUnexplored true if should also find not yet generated structures + * @param name the name of the structure * * @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..e5c4dd31 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/WorldgenRegistry.java @@ -0,0 +1,15 @@ +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; + +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. + IDependencyGraph terrain(); + + /// Populators finalize cubes and put the 'window dressing' on the world - ores, trees, etc. + IDependencyGraph 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..a354f052 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/ICubeGenerator.java @@ -0,0 +1,22 @@ +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..b64467dc 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,24 @@ * 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.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..4357175d --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/decoration/IWorldDecorator.java @@ -0,0 +1,30 @@ +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; + +/// 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/StandardWorldDecorator.java b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java new file mode 100644 index 00000000..2826a411 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/api/worldgen/impl/StandardWorldDecorator.java @@ -0,0 +1,57 @@ +package com.cardinalstar.cubicchunks.api.worldgen.impl; + +import net.minecraft.world.World; + +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 DependencyGraph terrain = new DependencyGraph<>(); + + private final DependencyGraph population = new DependencyGraph<>(); + + @Override + public IDependencyGraph terrain() { + return terrain; + } + + @Override + public IDependencyGraph 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 76% 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..5dcb702e 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,44 @@ * 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..82e3b8e0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/async/TaskPool.java @@ -0,0 +1,206 @@ +package com.cardinalstar.cubicchunks.async; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +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", "UnusedReturnValue" }) +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 Object QUEUE_LOCK = new Object(); + private static final ArrayDeque> TASK_QUEUE = new ArrayDeque<>(1024); + + private static final List THREADS = new ArrayList<>(); + + public static void init() { + THREADS.clear(); + + int count = CubicChunksConfig.optimizations.backgroundThreads; + + for (int i = 0; i < count; i++) { + Thread worker = new WorkerThread(); + THREADS.add(worker); + worker.start(); + } + } + + 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); + } + + @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(); + } + } + } + + 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 static Future submit(ITaskExecutor executor, TTask task) { + return submit(executor, task, null); + } + + public static Future submit(ITaskExecutor executor, TTask task, + @Nullable Consumer callback) { + TaskFuture future = new TaskFuture<>(task, callback); + + synchronized (QUEUE_LOCK) { + if (!TASK_QUEUE.isEmpty()) { + TaskContainer end = TASK_QUEUE.peekLast(); + + if (end.executor == executor) { + @SuppressWarnings("unchecked") + TaskContainer end2 = (TaskContainer) end; + + // noinspection unchecked + if (end2.executor.canMerge((List>) (List) end2.tasks, task)) { + end2.tasks.add(future); + QUEUE_LOCK.notify(); + return future; + } + } + } + + TaskContainer container = new TaskContainer<>(executor); + container.tasks.add(future); + + TASK_QUEUE.addLast(container); + QUEUE_LOCK.notify(); + return future; + } + } + + public static void flush() { + while (true) { + synchronized (QUEUE_LOCK) { + if (TASK_QUEUE.isEmpty()) return; + } + + Thread.yield(); + } + } + + public interface ITaskExecutor { + + void execute(List> tasks); + + default boolean canMerge(List> tasks, TTask task) { + return tasks.size() < 128; + } + } + + public interface ITaskFuture { + + TTask getTask(); + + void finish(TResult value); + + void fail(Throwable t); + } + + private static class TaskFuture extends AbstractFuture + implements ITaskFuture { + + public final TTask task; + @Nullable + private final Consumer callback; + + public TaskFuture(TTask task, @Nullable Consumer callback) { + this.task = task; + this.callback = callback; + } + + @Override + public TTask getTask() { + return task; + } + + @Override + public void finish(TResult value) { + set(value); + if (callback != null) callback.accept(value); + } + + @Override + 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/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..e0fb70bc --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/client/WorldDiagnostics.java @@ -0,0 +1,39 @@ +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..101f3e4c 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,31 @@ 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 8875432b..88ae8d62 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java +++ b/src/main/java/com/cardinalstar/cubicchunks/event/handlers/ClientEventHandler.java @@ -20,34 +20,24 @@ */ 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.world.ICubicWorldType; -import com.cardinalstar.cubicchunks.api.worldgen.VanillaCompatibilityGeneratorProviderBase; +import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings; 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; @@ -91,52 +81,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 +97,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 +127,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 +161,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 +171,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()); } /** @@ -237,114 +195,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/lighting/FirstLightProcessor.java b/src/main/java/com/cardinalstar/cubicchunks/lighting/FirstLightProcessor.java index e6605aa2..ba88f37e 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,21 @@ 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 211d4e1e..e55280d2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -68,6 +68,20 @@ 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) + .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 MIXIN_CHUNK_COLUMN( @@ -162,6 +176,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( @@ -261,7 +279,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/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/MixinEntityRenderer.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntityRenderer.java index eaa34160..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 @@ -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 d5ea94ee..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 @@ -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,12 +56,11 @@ 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 public void tickCubicWorld() { - getLightingManager().onTick(); + if (getLightingManager() != null) getLightingManager().onTick(); } @Override @@ -70,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 674abbb5..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; @@ -174,8 +175,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(); @@ -252,7 +252,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(); @@ -269,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( @@ -305,6 +306,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; } @@ -521,7 +526,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]; } // ============================================== @@ -700,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)); } @@ -725,8 +732,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/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/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..719c58d8 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinExtendedBlockStorage.java @@ -0,0 +1,29 @@ +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 final 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/MixinRegionFileCache.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinRegionFileCache.java index 472922f0..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 @@ -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/MixinWorld.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorld.java index 73a3f7ba..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; @@ -51,7 +53,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,29 +65,29 @@ 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; 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; +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 +218,51 @@ 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) { @@ -420,9 +383,29 @@ 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().checkLightFor(lightType, x, y, z)); + ci.setReturnValue(getLightingManager() != null && getLightingManager().checkLightFor(lightType, x, y, z)); } /** @@ -452,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/MixinWorldProvider.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinWorldProvider.java index b56489aa..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 @@ -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,18 @@ 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..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 @@ -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,19 @@ 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)) @@ -305,24 +321,15 @@ 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 - CubicPlayerManager.TickableChunkContainer chunks = ((CubicPlayerManager) this.thePlayerManager) - .getTickableChunks(); - for (Chunk chunk : chunks.columns()) { + for (Chunk chunk : ((CubeProviderServer) this.theChunkProviderServer).getTickableChunks()) { 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; - } + + for (Cube cube : ((CubeProviderServer) this.theChunkProviderServer).getTickableCubes()) { tickCube(cube, worldTime); } @@ -423,4 +430,13 @@ 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..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 @@ -532,4 +532,12 @@ 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..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 @@ -1,30 +1,76 @@ package com.cardinalstar.cubicchunks.mixin.early.common.worldgen; -import net.minecraft.block.Block; -import net.minecraft.world.World; -import net.minecraft.world.chunk.IChunkProvider; +import java.util.Random; + +import net.minecraft.world.ChunkCoordIntPair; 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.llamalad7.mixinextras.expression.Definition; -import com.llamalad7.mixinextras.expression.Expression; +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.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @Mixin(ChunkProviderGenerate.class) -public class MixinChunkProviderGenerate { - - @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_) { +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; + + @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)); + } + + @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); + } + } else { + return; + } + } + + 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/modcompat/ModWorldgenInit.java b/src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java deleted file mode 100644 index 7d1a96f0..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/modcompat/ModWorldgenInit.java +++ /dev/null @@ -1,18 +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/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..44d85ce0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/modcompat/angelica/IAngelicaDelegate.java @@ -0,0 +1,13 @@ +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/CCPacketBuffer.java b/src/main/java/com/cardinalstar/cubicchunks/network/CCPacketBuffer.java index 350476c3..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,55 @@ 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); + + 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/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/PacketEncoderColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderColumn.java index 673bddc6..b9ea141f 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; @@ -78,10 +79,15 @@ 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/PacketEncoderCubeBlockChange.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java index 0e3cc4cf..f9df94b7 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java +++ b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderCubeBlockChange.java @@ -20,24 +20,33 @@ */ 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 gnu.trove.TShortCollection; +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; @@ -46,8 +55,8 @@ 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() { @@ -57,21 +66,39 @@ public byte getPacketID() { public PacketEncoderCubeBlockChange() {} - public static PacketCubeBlockChange createPacket(Cube cube, TShortCollection localAddresses) { + @SuppressWarnings("DataFlowIssue") + 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]; + List updates = new ArrayList<>(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); + + 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)); } @@ -89,7 +116,7 @@ public static PacketCubeBlockChange createPacket(Cube cube, TShortCollection loc i++; } - return new PacketCubeBlockChange(cubePos, localAddressArray, blocks, blockMetas, heightValues); + return new PacketCubeBlockChange(cubePos, heightValues, updates); } @Override @@ -100,49 +127,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(); - - short[] addresses = new short[blockCount]; - Block[] blocks = new Block[blockCount]; - int[] metas = new int[blockCount]; + List updates = buffer.readList(buffer1 -> { + S23PacketBlockChange packet = new S23PacketBlockChange(); - for (short i = 0; i < blockCount; i++) { - addresses[i] = buffer.readShort(); - blocks[i] = buffer.readBlock(); - metas[i] = buffer.readBlockMeta(); - } - - int heightmapCount = buffer.readUnsignedByte(); - int[] heightValues = new int[heightmapCount]; + try { + packet.readPacketData(buffer1); + } catch (IOException e) { + throw new UncheckedIOException(e); + } - for (int i = 0; i < heightmapCount; i++) { - heightValues[i] = buffer.readInt(); - } + return packet; + }); - 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(); @@ -156,6 +173,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; @@ -163,12 +181,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 a82d595e..fba5fecf 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,7 +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.util.CubeStatusVisualizer; +import com.cardinalstar.cubicchunks.util.CubeStatusVisualizer.CubeStatus; import com.cardinalstar.cubicchunks.world.cube.Cube; import com.github.bsideup.jabel.Desugar; @@ -57,7 +59,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) @@ -67,6 +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); } ByteBuf cubeData = Unpooled.buffer(); @@ -79,7 +84,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()); @@ -95,7 +100,7 @@ public static PacketCubes createPacket(List cubes) { tileEntityTags.add(list); } - }); + } return new PacketCubes(cubePos, data, tileEntityTags); } @@ -146,9 +151,16 @@ 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/PacketEncoderHeightMapUpdate.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderHeightMapUpdate.java index ce2d6ef7..4ba098be 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,25 @@ 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 +56,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 +76,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 +105,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/network/PacketEncoderUnloadColumn.java b/src/main/java/com/cardinalstar/cubicchunks/network/PacketEncoderUnloadColumn.java index bff4414f..7067d9f9 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,10 @@ 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..2aac913e 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,10 @@ public void process(World world, PacketUnloadCube packet) { cubeCache.getCube(packet.pos) .markForRenderUpdate(); cubeCache.unloadCube(packet.pos); + + if (AngelicaInterop.hasDelegate()) { + AngelicaInterop.getDelegate() + .onCubeUnloaded(packet.pos.getX(), packet.pos.getY(), packet.pos.getZ()); + } } } 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..c3316242 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,23 @@ 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/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 deleted file mode 100644 index 0c8fa2a4..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/server/ColumnWatcher.java +++ /dev/null @@ -1,255 +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; -import com.cardinalstar.cubicchunks.world.api.ICubeProviderServer; - -@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; - - 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 - @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 (request != null) { - request.cancel(); - } - - 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 194b63a9..5fddbe5b 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,42 +39,42 @@ 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; -import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.biome.BiomeGenBase; 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; 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.google.common.collect.ImmutableSetMultimap; +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; 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. @@ -99,20 +101,12 @@ public class CubeProviderServer extends ChunkProviderServer // @Nonnull private final CubePrimer cubePrimer; @Nonnull - private final ICubeGenerator cubeGen; + private final IWorldGenerator worldGenerator; @Nonnull private final Profiler profiler; - private final WatchersSortingList3D eagerCubeLoads = new WatchersSortingList3D<>( - 0, - this::getPlayers); - - private final WatchersSortingList2D eagerColumnLoads = new WatchersSortingList2D<>( - 0, - this::getPlayers); - - private int columnsLoadedThisTick = 0; - private int cubesLoadedThisTick = 0; + private final Map eagerLoads = new Object2ObjectOpenHashMap<>(); + private final List eagerLoadOrder = new ArrayList<>(); private static final int MAX_NS_SPENT_LOADING = 10_000_000; @@ -122,7 +116,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 +124,25 @@ 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) + format.getFormat() .provideStorage(worldServer, path), - cubeGen, + worldGenerator, new LoadingCallbacks()); } catch (IOException e) { throw new UncheckedIOException(e); @@ -172,8 +165,6 @@ public void onColumnLoaded(Chunk column) { pendingAsyncChunkLoads.removeAll(new ChunkCoordIntPair(column.xPosition, column.zPosition)) .forEach(Runnable::run); - columnsLoadedThisTick++; - callbacks.forEach(c -> c.onColumnLoaded(column)); } @@ -188,15 +179,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, CubeLoaderServer.CubeInitLevel newLevel) { - cubesLoadedThisTick++; - + public void onCubeGenerated(Cube cube, CubeInitLevel newLevel) { callbacks.forEach(c -> c.onCubeGenerated(cube, newLevel)); } @@ -206,13 +193,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 @@ -271,7 +277,6 @@ public Chunk provideColumn(int cubeX, int cubeZ) { } @Override - @Deprecated public Chunk provideChunk(int cubeX, int cubeZ) { return provideColumn(cubeX, cubeZ); } @@ -304,120 +309,110 @@ public void tick() { Random rand = this.worldObj.rand; BooleanSupplier tickFaster = () -> System.currentTimeMillis() - start > 40; - profiler.startSection("Tick force loaded cubes"); - - for (Cube cube : getForceLoadedCubes()) { - cube.tickCubeServer(tickFaster, rand); - } - - profiler.endStartSection("Tick watched cubes"); + profiler.startSection("Tick cubes"); - for (Cube cube : ((CubicPlayerManager) worldServer.getPlayerManager()).getWatchedCubes()) { + for (Cube cube : getTickableCubes()) { cube.tickCubeServer(tickFaster, rand); } profiler.endSection(); - if (columnsLoadedThisTick > 0) { - CubicChunks.LOGGER.info("Loaded {} columns this tick", columnsLoadedThisTick); - } - - if (cubesLoadedThisTick > 0) { - CubicChunks.LOGGER.info("Loaded {} cubes this tick", cubesLoadedThisTick); - } + getCubeLoader().setNow(worldObj.getTotalWorldTime()); doEagerLoading(); - - columnsLoadedThisTick = 0; - cubesLoadedThisTick = 0; } private void doEagerLoading() { profiler.startSection("Eager object sorting"); - eagerColumnLoads.tick(); - eagerCubeLoads.tick(); + // TODO: make this faster + eagerLoadOrder.sort( + Comparator.comparingInt(this::getChunkDistanceSquared) + .reversed()); 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; + int processed = 0; + int startCols = eagerLoadOrder.size(); - cubeLoader.getColumn(request.pos.chunkXPos, request.pos.chunkZPos, request.effort); + while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && !eagerLoadOrder.isEmpty()) { + ChunkCoordIntPair coord = eagerLoadOrder.get(eagerLoadOrder.size() - 1); - columns++; - } + EagerCubeLoadContainer container = eagerLoads.get(coord); - Iterator cubeIter = eagerCubeLoads.iterator(); + if (container == null) { + eagerLoads.remove(coord); + eagerLoadOrder.remove(eagerLoadOrder.size() - 1); + continue; + } - while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { - EagerCubeLoadRequest request = cubeIter.next(); - cubeIter.remove(); - request.completed = true; + Iterator cubeIter = container.cubes.values() + .iterator(); - Cube cube = cubeLoader.getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); + while ((System.nanoTime() - start) < MAX_NS_SPENT_LOADING && cubeIter.hasNext()) { + EagerCubeLoadRequest request = cubeIter.next(); - CubeLoaderServer.CubeInitLevel actual = cube == null ? CubeLoaderServer.CubeInitLevel.None - : cube.getInitLevel(); - CubeLoaderServer.CubeInitLevel wanted = CubeLoaderServer.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); - } + processed++; - cubes++; - } + if (request.isCancelled()) continue; - if (columns > 0) { - CubicChunks.LOGGER.info("Processed {} eager column loads this tick", columns); - } + cubeLoader.pauseLoadCalls(); - if (cubes > 0) { - CubicChunks.LOGGER.info("Processed {} eager cube loads this tick", cubes); - } + Cube cube = cubeLoader + .getCube(request.pos.getX(), request.pos.getY(), request.pos.getZ(), request.effort); - profiler.endSection(); - } + cubeLoader.unpauseLoadCalls(); - private List getForceLoadedCubes() { - ImmutableSetMultimap persistentChunks = ForgeChunkManager - .getPersistentChunksFor(worldServer); + CubeInitLevel actual = cube == null ? CubeInitLevel.None : cube.getInitLevel(); + CubeInitLevel wanted = CubeInitLevel.fromRequirement(request.effort); - worldServer.theProfiler.startSection("forcedChunkLoading"); + 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); + } + } - ArrayList loadedCubes = new ArrayList<>(); + if (container.cubes.isEmpty()) { + eagerLoads.remove(coord); + eagerLoadOrder.remove(eagerLoadOrder.size() - 1); + } else { + break; + } + } - for (ChunkCoordIntPair pos : persistentChunks.keySet()) { - @SuppressWarnings("unchecked") - Collection cubes = (Collection) ((IColumn) worldServer - .getChunkFromChunkCoords(pos.chunkXPos, pos.chunkZPos)).getLoadedCubes(); + long delta = System.nanoTime() - start; - for (Cube cube : cubes) { - if (cube == null) continue; - if (cube.isEmpty()) continue; - if (!cube.isPopulated()) continue; + if (delta > MAX_NS_SPENT_LOADING * 2) { + CubicChunks.LOGGER.warn("Spent {} ms loading the world this tick", delta / 1e6); + } - loadedCubes.add(cube); - } + if (processed > 0) { + CubicChunks.LOGGER.info( + "Processed {} eager load requests this tick ({} -> {} columns)", + processed, + startCols, + eagerLoadOrder.size()); } - worldServer.theProfiler.endSection(); + profiler.endSection(); + } + + public Collection getTickableChunks() { + return ((CubicPlayerManager) worldServer.getPlayerManager()).getColumns(); + } - return loadedCubes; + public Collection getTickableCubes() { + return ((CubicPlayerManager) worldServer.getPlayerManager()).getCubes(); } @Override @@ -427,7 +422,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 @@ -463,61 +458,19 @@ public Cube getLoadedCube(CubePos coords) { return getLoadedCube(coords.getX(), coords.getY(), coords.getZ()); } - @SuppressWarnings("unused") - public class EagerColumnLoadRequest implements XZAddressable, BucketSorterEntry { + public static class EagerCubeLoadContainer implements XZAddressable { 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 final Int2ObjectRBTreeMap cubes = new Int2ObjectRBTreeMap<>( + IntComparator.comparingInt(i -> -i)); - 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); + public EagerCubeLoadContainer(ChunkCoordIntPair pos) { + this.pos = 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; + public void add(EagerCubeLoadRequest request) { + cubes.put(request.getY(), request); } @Override @@ -532,31 +485,22 @@ public int getZ() { } @SuppressWarnings("unused") - public class EagerCubeLoadRequest implements XYZAddressable, BucketSorterEntry { + public static class EagerCubeLoadRequest implements XYZAddressable { public final CubePos pos; + @Setter + @Getter private Requirement effort; - private boolean completed; - - public EagerCubeLoadRequest(int cubeX, int cubeY, int cubeZ, Requirement effort) { - this.pos = new CubePos(cubeX, cubeY, cubeZ); - this.effort = effort; - } + @Getter + private boolean completed, cancelled; - public Requirement getEffort() { - return effort; - } - - public void setEffort(Requirement effort) { + public EagerCubeLoadRequest(CubePos pos, Requirement effort) { + this.pos = pos; this.effort = effort; } - public boolean isCompleted() { - return completed; - } - public void cancel() { - CubeProviderServer.this.eagerCubeLoads.remove(this); + this.cancelled = true; } @Override @@ -576,18 +520,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(); @@ -604,18 +536,24 @@ public int getZ() { } } - public EagerColumnLoadRequest loadColumnEagerly(int x, int z, Requirement effort) { - EagerColumnLoadRequest request = new EagerColumnLoadRequest(x, z, effort); + public EagerCubeLoadRequest loadCubeEagerly(int x, int y, int z, Requirement effort) { + CubePos pos = new CubePos(x, y, z); + + ChunkCoordIntPair coord = new ChunkCoordIntPair(x, z); - eagerColumnLoads.add(request); + EagerCubeLoadContainer container = eagerLoads.get(coord); - return request; - } + if (container == null) { + container = new EagerCubeLoadContainer(coord); + eagerLoads.put(coord, container); + eagerLoadOrder.add(coord); + } - public EagerCubeLoadRequest loadCubeEagerly(int x, int y, int z, Requirement effort) { - EagerCubeLoadRequest request = new EagerCubeLoadRequest(x, y, z, effort); + EagerCubeLoadRequest request = new EagerCubeLoadRequest(pos, effort); + + container.add(request); - 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 deleted file mode 100644 index f72fdc84..00000000 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubeWatcher.java +++ /dev/null @@ -1,442 +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.CubeLoaderServer; -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(CubeLoaderServer.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() != CubeLoaderServer.CubeInitLevel.Lit) { - if (request != null && request.isCompleted()) { - request = cubeCache.loadCubeEagerly( - cubePos.getX(), - cubePos.getY(), - cubePos.getZ(), - ICubeProviderServer.Requirement.LIGHT); - } - - return; - } - this.cube = c; - this.cube.getTickets() - .add(this); - 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); - } - } - - 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.isPopulated() - || !this.cube.isInitialLightingDone() - || !this.cube.isSurfaceTracked(); - } - - 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 - this.players.forEach(entry -> 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 e2f0b6ec..db368948 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/CubicPlayerManager.java @@ -21,61 +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.server.chunkio.CubeLoaderServer; +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 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; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; /** * A cubic chunks implementation of Player Manager. @@ -86,98 +93,21 @@ 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. + * Mapping of entityId to PlayerCubeMap.PlayerWrapper objects. */ - private final CubeSelector cubeSelector = new CuboidalCubeSelector(); + private final Int2ObjectOpenHashMap players = new Int2ObjectOpenHashMap<>(); - /** - * Mapping if entityId to PlayerCubeMap.PlayerWrapper objects. - */ - private final TIntObjectMap players = new TIntObjectHashMap<>(); - - /** - * 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<>(); + private final CubeProviderServer provider; - /** - * 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 XYZMap watchedCubes = new XYZMap<>(); + private final Set dirtyCubes = new ObjectOpenHashSet<>(); - /** - * All cubeWatchers that have pending block updates to send. - */ - private final Set cubeWatchersToUpdate = new HashSet<>(); - - /** - * All columnWatchers that have pending height updates to send. - */ - private final Set columnWatchersToUpdate = new HashSet<>(); - - /** - * 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.valueCollection() - .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.valueCollection() - .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.valueCollection() - .stream() - .map(p -> p.playerEntity) - .collect(Collectors.toList())); - - private final WatchersSortingList3D tickableCubeTracker = new WatchersSortingList3D<>( - 5, - () -> players.valueCollection() - .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 @@ -185,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); @@ -199,72 +125,71 @@ public CubicPlayerManager(WorldServer worldServer) { .getViewDistance(), ((ICubicPlayerList) worldServer.func_73046_m() .getConfigurationManager()).getVerticalViewDistance()); - // this.vanillaNetworkHandler = ((ICubicWorldInternal.Server) worldServer).getVanillaNetworkHandler(); - ((ICubicWorldInternal.Server) worldServer).getCubeCache() - .registerCallback(this); + + provider = ((Server) worldServer).getCubeCache(); + provider.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 Collection getColumns() { + return new AbstractCollection<>() { - public TickableChunkContainer getTickableChunks() { - TickableChunkContainer tickableChunksCubes = this.tickableChunksCubesToReturn; - tickableChunksCubes.clear(); - addTickableColumns(tickableChunksCubes); - addTickableCubes(tickableChunksCubes); - addForcedColumns(tickableChunksCubes); - addForcedCubes(tickableChunksCubes); - return tickableChunksCubes; - } + @Override + public @NotNull Iterator iterator() { + return new AbstractIterator<>() { - private void addForcedColumns(TickableChunkContainer tickableChunksCubes) { - for (IColumn columns : ((ICubicWorldInternal.Server) getWorldServer()).getForcedColumns()) { - tickableChunksCubes.addColumn((Chunk) columns); - } - } + final Iterator iter = watchedColumns.iterator(); - private void addForcedCubes(TickableChunkContainer tickableChunksCubes) { - tickableChunksCubes.forcedCubes = ((ICubicWorldInternal.Server) getWorldServer()).getForcedCubes(); - } + @Override + protected Chunk computeNext() { + while (iter.hasNext()) { + WatchedColumn column = iter.next(); + + if (column.column == null) continue; - private void addTickableCubes(TickableChunkContainer tickableChunksCubes) { - for (CubeWatcher watcher : (Iterable) () -> tickableCubeTracker.iteratorUpToDistance(9)) { - ICube cube = watcher.getCube(); - if (cube == null) { - continue; + return column.column; + } + + return this.endOfData(); + } + }; } - tickableChunksCubes.addCube(cube); - } + + @Override + public int size() { + return watchedColumns.getSize(); + } + }; } - 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(); + } + }; } /** @@ -276,177 +201,188 @@ 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)); + + List fullSync = new ObjectArrayList<>(); + + List notReady = new ArrayList<>(); + + for (WatchedCube cube : dirtyCubes) { + if (cube.cube == null) continue; - while (toSend > 0 && iter.hasNext()) { - CubeWatcher cubeWatcher = iter.next(); - cubeWatcher.addScheduledPlayers(); + switch (cube.dirty) { + case None -> { + // huh? + } + case Partial -> { + cube.syncPartial(); + cube.clean(); + } + case Full -> { + WatchedColumn col = watchedColumns.get(cube.getX(), cube.getZ()); + + if (col == null || col.column == null) { + notReady.add(cube); + continue; + } - if (cubeWatcher.sendToPlayers()) { - iter.remove(); - toSend--; + 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(); + dirtyCubes.addAll(notReady); - 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 + .getOrCreateWatchedColumn(new ChunkCoordIntPair(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 onCubeGenerated(Cube cube, CubeLoaderServer.CubeInitLevel newLevel) { - CubeWatcher watcher = this.cubeWatchers.get(cube); + public void onCubeLoaded(Cube 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 onCubeGenerated(Cube cube, CubeInitLevel newLevel) { + 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 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 @@ -461,24 +397,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; } @@ -486,39 +411,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); - - if (columnWatcher == null) { - this.columnWatchers.put(columnWatcher = new ColumnWatcher(this, chunkPos)); + private WatchedColumn getOrCreateWatchedColumn(ChunkCoordIntPair chunkPos) { + WatchedColumn watchedColumn = this.watchedColumns.get(chunkPos.chunkXPos, chunkPos.chunkZPos); - 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(CubePos.fromBlockCoords(x, y, z)); + 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); } } @@ -539,68 +456,65 @@ 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 + CuboidalCubeSelector.INSTANCE .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); - scheduleAddPlayerToWatcher(cubeWatcher, player); + getOrCreateWatchedColumn(currentPos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(currentPos).addPlayer(watchingPlayer); }); - this.players.put(player.getEntityId(), playerWrapper); + + 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) -> { - - // 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); - } + ObjectSet unloadedColumns = new ObjectOpenHashSet<>( + (horizontalViewDistance * 2 + 1) * (horizontalViewDistance * 2 + 1)); - // remove column watchers if needed - ColumnWatcher columnWatcher = getColumnWatcher(cubePos.chunkPos()); - if (columnWatcher == null) { - return; - } + CuboidalCubeSelector.INSTANCE + .forAllVisibleFrom(playerCubePos, horizontalViewDistance, verticalViewDistance, (cubePos) -> { + // get the watcher + WatchedCube cube = watchedCubes.get(cubePos); - toSendUnload.add(columnWatcher); - }); - toSendUnload.stream() - .filter(watcher -> watcher.containsPlayer(player)) - .forEach(watcher -> watcher.removePlayer(player)); - this.players.remove(player.getEntityId()); - // vanillaNetworkHandler.removePlayer(player); + 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()); + + 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); + } } // CHECKED: 1.10.2-12.18.1.2092 @@ -611,23 +525,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 +562,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,75 +583,95 @@ private void updatePlayer(PlayerWrapper entry, CubePos oldPos, CubePos newPos) { columnsToRemove, columnsToLoad); - getWorldServer().theProfiler.endStartSection("createColumns"); // order is important, columns first - columnsToLoad.forEach(pos -> { - ColumnWatcher columnWatcher = this.getOrCreateColumnWatcher(pos); - assert columnWatcher.getPos() - .equals(pos); - columnWatcher.addPlayer(entry.playerEntity); - }); + + getWorldServer().theProfiler.endStartSection("createColumns"); + columnsToLoad.forEach( + pos -> { + 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); - }); + cubesToLoad.forEach( + pos -> { + 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)) { - cubeCache.getCube( - newPos.getX() + v.x(), - newPos.getY() + v.y(), - newPos.getZ() + v.z(), - ICubeProviderServer.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 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 +709,24 @@ public final void setPlayerViewDistance(int newHorizontalViewDistance, int newVe return; } - for (PlayerWrapper playerWrapper : this.players.valueCollection()) { + 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 + CuboidalCubeSelector.INSTANCE .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); - } + getOrCreateWatchedColumn(pos.chunkPos()).addPlayer(watchingPlayer); + getOrCreateCubeWatcher(pos).addPlayer(watchingPlayer); }); - // either both got smaller or only one of them changed } 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 +736,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,90 +752,40 @@ 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(); - // } - - void addToUpdateEntry(CubeWatcher cubeWatcher) { - this.cubeWatchersToUpdate.add(cubeWatcher); - } - - void addToUpdateEntry(ColumnWatcher columnWatcher) { - this.columnWatchersToUpdate.add(columnWatcher); - } - - // 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 - } - // 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(CubePos pos) { - return this.cubeWatchers.get(pos.getX(), pos.getY(), pos.getZ()); - } + private static class WatchingPlayer { - @Nullable - public ColumnWatcher getColumnWatcher(ChunkCoordIntPair pos) { - return this.columnWatchers.get(pos.chunkXPos, pos.chunkZPos); - } + public final EntityPlayerMP player; + private double managedPosY; + private final ArrayList cubeSendQueue = new ArrayList<>(20); - public boolean contains(CubePos coords) { - return this.cubeWatchers.get(coords.getX(), coords.getY(), coords.getZ()) != null; - } + WatchingPlayer(EntityPlayerMP player) { + this.player = player; + } - 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() { @@ -922,7 +793,7 @@ int getManagedCubePosY() { } int getManagedCubePosZ() { - return blockToCube(this.playerEntity.managedPosZ); + return blockToCube(this.player.managedPosZ); } CubePos getManagedCubePos() { @@ -931,66 +802,236 @@ 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 { - private final ObjectArrayList cubes = ObjectArrayList.wrap(new ICube[64 * 1024]); - private XYZMap forcedCubes; - private final Set columns = Collections.newSetFromMap(new IdentityHashMap<>()); + public EagerCubeLoadRequest request; + public Cube cube; + public final ShortArrayList dirtyBlocks = new ShortArrayList(8); + public final ArrayList watchingPlayers = new ArrayList<>(1); - private void clear() { - this.cubes.clear(); - this.columns.clear(); + private Dirtiness dirty = Dirtiness.None; + + public WatchedCube(int cubeX, int cubeY, int cubeZ) { + super(cubeX, cubeY, cubeZ); + + setCube(provider.getLoadedCube(cubeX, cubeY, cubeZ)); + } + + 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 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); } - private void addCube(ICube cube) { - cubes.add(cube); + public void requestFullSync() { + this.dirty = Dirtiness.Full; + dirtyBlocks.clear(); + CubicPlayerManager.this.dirtyCubes.add(this); } - public void addColumn(Chunk column) { - columns.add(column); + 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; + CubeStatusVisualizer.put(new CubePos(this), CubeStatus.Synced); } - 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 (short localAddress : dirtyBlocks) { + 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/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/CCNBTUtils.java b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java new file mode 100644 index 00000000..002f7273 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CCNBTUtils.java @@ -0,0 +1,50 @@ +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) { + 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); + } + + 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 new file mode 100644 index 00000000..4642da35 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeIO.java @@ -0,0 +1,386 @@ +package com.cardinalstar.cubicchunks.server.chunkio; + +import java.io.IOException; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Future; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.chunk.Chunk; +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; +import com.cardinalstar.cubicchunks.async.TaskPool; +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.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 { + + 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 Object2ObjectLinkedOpenHashMap columnCache = new Object2ObjectLinkedOpenHashMap<>(); + + private final Object2ObjectLinkedOpenHashMap cubeCache = new Object2ObjectLinkedOpenHashMap<>(); + + interface Savable { + } + + @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; + 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(Optional.ofNullable(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(Optional.ofNullable(tag)); + } + } catch (IOException e) { + for (var task : tasks) { + task.fail(e); + } + } + }; + + 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) { + synchronized (columnCache) { + SaveData data = columnCache.get(pos); + + if (data != null) return data.tag != null; + } + + 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) { + synchronized (cubeCache) { + SaveData data = cubeCache.get(pos); + + if (data != null) return data.tag != null; + } + + 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 { + synchronized (columnCache) { + SaveData data = columnCache.get(pos); + + if (data != null) { + return data.tag == null ? null : (NBTTagCompound) data.tag.copy(); + } + } + + try { + NBTTagCompound tag = storage.readColumn(pos); + + if (tag == null) { + synchronized (columnCache) { + columnCache.put(pos, new SaveData(null, null, System.currentTimeMillis())); + } + } + + 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 { + synchronized (cubeCache) { + SaveData data = cubeCache.get(pos); + + if (data != null) { + return data.tag == null ? null : (NBTTagCompound) data.tag.copy(); + } + } + + try { + NBTTagCompound tag = storage.readCube(pos); + + if (tag == null) { + synchronized (cubeCache) { + cubeCache.put(pos, new SaveData(null, null, System.currentTimeMillis())); + } + } + + 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); + + column.isModified = false; + + 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) { + 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; + + Future task = TaskPool.submit(saveExecutor, new SaveCube(pos, tag)); + + long now = System.currentTimeMillis(); + + 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 { + TaskPool.flush(); + + this.storage.flush(); + } + + @Override + public void close() throws IOException { + TaskPool.flush(); + + while (true) { + synchronized (columnCache) { + if (columnCache.isEmpty()) break; + + columnCache.object2ObjectEntrySet() + .removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + } + + Thread.yield(); + } + + while (true) { + synchronized (cubeCache) { + if (cubeCache.isEmpty()) break; + + cubeCache.object2ObjectEntrySet() + .removeIf(e -> e.getValue().task == null || e.getValue().task.isDone()); + } + + Thread.yield(); + } + + this.storage.close(); + } + + @Override + public void preloadColumn(ChunkCoordIntPair pos) { + TaskPool.submit(columnLoadExecutor, pos, tag -> { + if (!tag.isPresent()) { + if (preloadFailures != null) preloadFailures.onColumnPreloadFailed(pos); + } else { + synchronized (columnCache) { + columnCache.put(pos, new SaveData(tag.orElse(null), null, System.currentTimeMillis())); + } + } + }); + } + + @Override + public void preloadCube(CubePos pos, CubeInitLevel wanted) { + TaskPool.submit(cubeLoadExecutor, pos, tag -> { + CubeInitLevel actual = !tag.isPresent() ? CubeInitLevel.None : IONbtReader.getCubeInitLevel(tag.get()); + + 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/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..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,10 +24,10 @@ 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, 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..79d55dd2 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/CubeLoaderServer.java @@ -1,90 +1,95 @@ 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 net.minecraftforge.event.world.ChunkDataEvent; 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; 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.server.CubicPlayerManager; +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; +import lombok.Setter; + +public class CubeLoaderServer implements ICubeLoader { -public class CubeLoaderServer implements IThreadedFileIO, ICubeLoader { + private static final MetaKey CUBE_INFO = new MetaKey<>() {}; 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; + @Setter + private long now; + + 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,28 +133,53 @@ 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) { - CubeInfo cube = cubes.get(x, y, 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 info = cubes.get(x, y, z); + + Cube cube = info == null ? null : info.cube; - return cube == null ? null : cube.cube; + if (cache == null) { + lastCube = cube; + } else { + cache.set(x, y, z, cube); + } + + return cube; } @Override 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); - } - - return false; + return cubeIO.cubeExists(new CubePos(x, y, z)); } @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 +193,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 +203,56 @@ 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); + } + + if (success) { + cubeInfo.lastAccess = now; + } + return success ? cubeInfo.cube : null; } + @Override + public void onCubeGenerated(Cube cube) { + CubeInfo cubeInfo = cube.getMeta(CUBE_INFO); + + 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) { + cache = new Array3D<>(spanx, spany, spanz, x, y, z, new Cube[spanx * spany * spanz]); + } + + @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 +274,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 +301,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,37 +320,35 @@ public void saveCube(Cube cube) { return; } - saveCube(cubeInfo); + cubeIO.saveCube(cubeInfo.pos, cube); } - 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)); - } + 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); @@ -312,12 +359,7 @@ 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()); - } + int autoCols = columns.getSize(); List pendingColumnUnloads = new ArrayList<>(); @@ -332,8 +374,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); } @@ -342,83 +383,82 @@ public void doGC() { unloadColumn(column); } - if (!pendingColumnUnloads.isEmpty()) { - CubicChunks.LOGGER.info( - "Garbage collected {} columns (now have {} columns loaded)", - pendingColumnUnloads.size(), - columns.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); } - // 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, 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; + } - do { - ThreadedFileIOBase.threadedIOInstance.queueIO(this); + if (info == null) { + info = new ColumnInfo(column.xPosition, column.zPosition); + columns.put(info); + } - ThreadedFileIOBase.threadedIOInstance.waitForFinish(); - } while (!this.pendingColumns.isEmpty() || !this.pendingCubes.isEmpty()); - } + info.source = ObjectSource.GeneratedSideEffect; + info.column = column; - @Override - public boolean writeNextIO() { - try { - ArrayList> columns = new ArrayList<>(); - ArrayList> cubes = new ArrayList<>(); + info.onColumnLoaded(); + } + } - // Consume all dirty cubes and columns - this.columnQueue.drainTo(columns); - this.cubeQueue.drainTo(cubes); + if (doCubes) { + for (Cube cube : result.cubeSideEffects) { + CubeInfo info = cubes.get(cube.getX(), cube.getY(), cube.getZ()); - Map columnMap = new ConcurrentHashMap<>(); - Map cubeMap = new ConcurrentHashMap<>(); + if (info != null && info.cube != null) { + CubicChunks.LOGGER + .warn("Worldgen side-effect replaced cube at {},{},{}!", cube.getX(), cube.getY(), cube.getZ()); + continue; + } - // 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()); + 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; + cube.setMeta(CUBE_INFO, info); - // 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.ensureColumn(Requirement.GET_CACHED); + + 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 +467,7 @@ private class ColumnInfo implements XZAddressable { public NBTTagCompound tag; public Chunk column; + public ObjectSource source; public final ObjectOpenHashSet containedCubes = new ObjectOpenHashSet<>(); @@ -444,7 +485,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; @@ -455,43 +496,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, column.xPosition, column.zPosition, true, true); 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; } @@ -501,6 +535,8 @@ private boolean loadColumn() { if (column == null) return false; + EVENT_BUS.post(new ChunkDataEvent.Load(column, tag)); + onColumnLoaded(); this.tag = null; @@ -509,9 +545,15 @@ private boolean loadColumn() { } public void onColumnLoaded() { + column.lastSaveTime = world.getTotalWorldTime(); + + ((IColumnInternal) column).setColumn(true); + column.onChunkLoad(); - if (!pauseLoadCalls) { + CubeLoaderServer.this.generator.recreateStructures(column); + + if (pauseLoadCalls == 0) { callback.onColumnLoaded(column); } else { pendingColumnLoads.add(column); @@ -520,7 +562,7 @@ public void onColumnLoaded() { public void onColumnUnloaded() { if (column.isModified) { - saveColumn(this); + cubeIO.saveColumn(pos, column); } column.onChunkUnload(); @@ -537,15 +579,12 @@ private class CubeInfo implements XYZAddressable { public ColumnInfo column; - private CubeSource source = CubeSource.None; + private ObjectSource source = ObjectSource.None; public boolean generating = false; + public long lastAccess = 0; - 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); @@ -571,8 +610,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(); @@ -584,7 +621,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,43 +634,43 @@ 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); - - for (BiConsumer> cons : asyncCallbacks) { - cons.accept(world, chunkLoadingData); - } - - this.tag = chunkLoadingData.getNbt(); - } + EVENT_BUS.post(event); - // 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; } 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); - source = CubeSource.Disk; + source = ObjectSource.Disk; onCubeLoaded(); @@ -657,81 +694,109 @@ 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) { - ensureColumn(); - + if (source == ObjectSource.None) { if (generating) { throw new IllegalStateException( "Cannot recursively generate a cube that is already being generated"); } - Optional 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 { - generating = true; + this.generating = true; - generated = generator.tryGenerateCube(column.column, pos.getX(), pos.getY(), pos.getZ(), true); + result = generator + .provideCube(column == null ? null : column.column, pos.getX(), pos.getY(), pos.getZ()); } finally { - generating = false; + this.generating = false; } - if (!generated.isPresent()) return false; + if (result == null) return false; + + this.cube = result.object; + this.source = ObjectSource.Generated; + cube.setMeta(CUBE_INFO, this); - cube = generated.get(); - source = CubeSource.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(); - saveCube(this); + // Inject the other cubes in this column + handleSideEffects(result, getX(), getZ(), false, true); } - 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); - } + // If this cube hasn't been populated at all, populate it. This generates any required cubes recursively. + 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); - } + // Do the initial lighting + if (!cube.isInitialLightingDone() || !cube.isSurfaceTracked()) { + ((ICubicWorldInternal) world).getLightingManager() + .doFirstLight(cube); + cube.setInitialLightingDone(true); + } - if (!cube.isSurfaceTracked()) { - cube.trackSurface(); - } + // Put the surface into the column (to update the column heightmap) as needed + if (!cube.isSurfaceTracked()) { + cube.trackSurface(); } return cube.getInitLevel() == CubeInitLevel.Lit; } 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(); ((IColumn) column.column).addCube(cube); column.containedCubes.add(this); cube.onCubeLoad(); - if (!pauseLoadCalls) { + CubeLoaderServer.this.generator.recreateStructures(cube); + + 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()); @@ -745,48 +810,10 @@ 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); } } } - - 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..225a14a2 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/ICubeIO.java @@ -0,0 +1,30 @@ +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..13731b8e 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,36 @@ 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(Cube cube); + + 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() + 1, + box.getY2() - box.getY1() + 1, + box.getZ2() - box.getZ1() + 1); + } + + void uncacheCubes(); void unloadCube(int x, int y, int z); @@ -36,11 +55,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..18bfb356 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IONbtReader.java @@ -38,16 +38,23 @@ 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 { @@ -59,11 +66,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 - return column; // TODO: use Chunk, not IColumn, whenever possible + ((IColumnInternal) column).setColumn(true); + return column; } @Nullable @@ -87,25 +101,30 @@ 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) { + 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) @@ -152,21 +171,37 @@ 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")); - // 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); @@ -178,32 +213,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); - - ExtendedBlockStorage ebs = new ExtendedBlockStorage( - Coords.cubeToMinBlock(cube.getY()), - !cube.getWorld().provider.hasNoSky); + private static void readBlocks(NBTTagCompound section, World world, Cube cube) { + 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.setBlockMetadataArray(new NibbleArray(nbt.getByteArray("BlocksMeta"), 4)); + ebs.setBlockLSBArray(section.getByteArray("Blocks")); + if (section.hasKey("Add")) { + ebs.setBlockMSBArray(new NibbleArray(section.getByteArray("Add"), 4)); + } - ebs.setBlocklightArray(new NibbleArray(nbt.getByteArray("BlockLight"), 4)); + ebs.setBlockMetadataArray(new NibbleArray(section.getByteArray("Data"), 4)); - if (!world.provider.hasNoSky) { - ebs.setSkylightArray(new NibbleArray(nbt.getByteArray("SkyLight"), 4)); - } + ebs.setBlocklightArray(new NibbleArray(section.getByteArray("BlockLight"), 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 @@ -334,31 +363,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 8666a0df..c77d9a8d 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; @@ -46,36 +43,41 @@ 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.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 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(); 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; } @@ -84,16 +86,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); - writeModData(cube, cubeNbt); + return cubeNbt; } @@ -104,16 +118,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 @@ -138,39 +142,22 @@ 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()); - - // 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); @@ -261,13 +248,18 @@ 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) { + ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); + try { + cube.writeBiomeArray(new CCPacketBuffer(buffer)); + + byte[] data = new byte[buffer.writerIndex()]; + buffer.readBytes(data); - private static void writeBiomes(Cube cube, NBTTagCompound nbt) {// biomes - byte[] biomes = cube.getBiomeArray(); - if (biomes != null) nbt.setByteArray("Biomes3D", biomes); + nbt.setByteArray("Biomes", data); + } finally { + buffer.release(); + } } private static List getScheduledTicks(Cube cube) { 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..2f75e6d8 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/server/chunkio/IPreloadFailureDelegate.java @@ -0,0 +1,15 @@ +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..4d3da278 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,11 +37,13 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.ChunkCoordIntPair; -import com.cardinalstar.cubicchunks.CubicChunks; +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; @@ -50,12 +51,13 @@ 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. @@ -75,7 +77,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,24 +86,18 @@ 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(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"))))); + (dir, key) -> Files.exists(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,20 +106,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(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"))))); + (dir, key) -> Files.exists(part3d.resolve(key.getName() + ".ext"))))); return new SaveCubeColumns(section2d, section3d); } else { @@ -157,22 +147,73 @@ 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 @@ -205,46 +246,30 @@ 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()))); - } - CubicChunks.LOGGER.info("Saved batch: {} columns and {} cubes", batch.columns.size(), batch.cubes.size()); - } 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(); @@ -258,17 +283,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(), true); } 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/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/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/BlockPosSet.java b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java new file mode 100644 index 00000000..53f09904 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BlockPosSet.java @@ -0,0 +1,70 @@ +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 new file mode 100644 index 00000000..5a6c8970 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/BooleanArray3D.java @@ -0,0 +1,85 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.BitSet; +import java.util.Iterator; + +import org.jetbrains.annotations.NotNull; +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)); + } + + @Override + public @NotNull 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); + } + + @Override + public BooleanArray2D clone() { + return (BooleanArray2D) super.clone(); + } +} 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 660d9721..cdbc54ed 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java +++ b/src/main/java/com/cardinalstar/cubicchunks/util/Coords.java @@ -154,4 +154,55 @@ 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 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 pack(x, X_SHIFT) | pack(y, Y_SHIFT) | pack(z, Z_SHIFT); + } + + public static int x(long key) { + return (int) unpack(key, X_SHIFT); + } + + public static int y(long key) { + return (int) unpack(key, Y_SHIFT); + } + + public static int z(long key) { + return (int) unpack(key, Z_SHIFT); + } } 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..640bc94c --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/CubeStatusVisualizer.java @@ -0,0 +1,101 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +import com.cardinalstar.cubicchunks.CubicChunksConfig; +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +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(); + + 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; + + 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/DataUtils.java b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java new file mode 100644 index 00000000..20322d64 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DataUtils.java @@ -0,0 +1,277 @@ +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/util/DependencyGraph.java b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java new file mode 100644 index 00000000..4d3f4b51 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/DependencyGraph.java @@ -0,0 +1,203 @@ +package com.cardinalstar.cubicchunks.util; + +import java.util.ArrayList; +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 implements IDependencyGraph { + + @Desugar + private record DepInfo(String dependency, boolean optional) { + + @Override + public 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 ObjectOpenHashSet targets = new ObjectOpenHashSet<>(); + + private final Multimap dependencies = MultimapBuilder.hashKeys() + .hashSetValues() + .build(); + + private List cachedSorted; + + 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, Optional.of(value)); + cachedSorted = null; + + for (String dep : deps) { + addUnparsedDependency(name, dep); + } + } + + 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 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 { + throw new IllegalArgumentException( + "Invalid dependency specification for object '" + object + "': '" + dep + "'"); + } + + cachedSorted = null; + } + + @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, optional)); + cachedSorted = null; + } + + @Override + public boolean removeDependency(String object, String dependsOn) { + Objects.requireNonNull(object, "object must not be null"); + Objects.requireNonNull(dependsOn, "dependsOn must not be null"); + + boolean successful = dependencies.remove(object, new DepInfo(dependsOn, false)); + cachedSorted = null; + + return successful; + } + + @Override + public void addTarget(String targetName, String... deps) { + Objects.requireNonNull(targetName, "targetName must not be 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() { + if (cachedSorted != null) return cachedSorted; + + ObjectLinkedOpenHashSet path = new ObjectLinkedOpenHashSet<>(); + + for (String node : dependencies.keys()) { + 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(); + + iterdeps: while (iter.hasNext()) { + String curr = iter.next(); + + for (DepInfo dep : dependencies.get(curr)) { + if (!added.contains(dep.dependency)) { + continue iterdeps; + } + } + + iter.remove(); + + added.add(curr); + + Optional value = objects.get(curr); + + if (value == null) { + throw new IllegalStateException("Missing value for key: " + curr); + } + + value.ifPresent(out::add); + } + } + + cachedSorted = ImmutableList.copyOf(out); + + return cachedSorted; + } + + 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 (!optional && !targets.contains(node) && !objects.containsKey(node)) { + throw new IllegalStateException( + node + " is present in the dependency graph but does not have a matching object"); + } + + path.add(node); + + 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/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/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/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/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 04a9f3e3..1eb2214d 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); @@ -85,7 +84,7 @@ public double nextDouble() { * * @return the current seed */ - public synchronized long getSeed() { + public long getSeed() { return seed; } @@ -96,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; } @@ -131,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; @@ -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; 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..4ce79c1b --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/BiomeArray.java @@ -0,0 +1,31 @@ +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(); + + @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); + } + + @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..ea29926e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/DynamicBiomeArray.java @@ -0,0 +1,95 @@ +package com.cardinalstar.cubicchunks.util.biome3d; + +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; + +/// 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(); + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public void clear() { + backing.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(); + + for (int i = 0; i < size; i++) { + reference.put(i, backing.get(i)); + } + + backing = reference; + + backing.put(key, value); + } + + // Boilerplate for Int2ObjectFunction.put + return null; + } + + @Override + 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); + } + + @Override + public BiomeGenBase defaultReturnValue() { + return backing.defaultReturnValue(); + } + + public void write(CCPacketBuffer buffer) { + NaiveCompression.compress(new NaiveCompressionDataInput() { + + @Override + public int size() { + return DynamicBiomeArray.this.size(); + } + + @Override + public int get(int index) { + return DynamicBiomeArray.this.get(index).biomeID; + } + }, buffer); + } + + public void read(CCPacketBuffer buffer) { + NaiveCompression.decompress(buffer, new NaiveCompressionDataOutput() { + + @Override + public int size() { + return DynamicBiomeArray.this.size(); + } + + @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/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 new file mode 100644 index 00000000..1d855930 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PaletteFullError.java @@ -0,0 +1,11 @@ +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() { + 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..6d1d5710 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/biome3d/PalettizedBiomeArray.java @@ -0,0 +1,140 @@ +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 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[] nibbleArray = new byte[16 * 16 * 16 / 2]; + + @Override + public boolean isEmpty() { + for (byte b : nibbleArray) { + if (b != 0) return false; + } + + return true; + } + + @Override + public void clear() { + Arrays.fill(nibbleArray, (byte) 0); + } + + @Override + public BiomeGenBase put(int key, BiomeGenBase value) { + int paletteIndex = paletteReversed.getInt(value); + + if (paletteIndex == -1) { + 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(); + + for (int i = 0; i < usedPaletteIndices.length; i++) { + boolean b = usedPaletteIndices[i]; + + if (b) continue; + + free = i; + break; + } + + if (free >= 16) { + throw new PaletteFullError(); + } + } + + paletteIndex = free; + palette[free] = value; + paletteReversed.put(value, free); + usedPaletteIndices[paletteIndex] = true; + } + + byte b = nibbleArray[key >> 1]; + + if ((key & 0b1) == 0) { + b = (byte) ((b & 0xF0) | paletteIndex); + } else { + b = (byte) ((paletteIndex << 4) | (b & 0xF)); + } + + nibbleArray[key >> 1] = b; + + return null; + } + + @Override + public BiomeGenBase get(int key) { + int index = key >> 1; + + int id = (key & 1) == 0 ? (this.nibbleArray[index] & 0xF) : (this.nibbleArray[index] >> 4 & 0xF); + + return palette[id]; + } + + @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() { + Arrays.fill(usedPaletteIndices, false); + usedPaletteIndices[0] = true; + + for (byte b : nibbleArray) { + int lower = b & 0xF; + int upper = (b >> 4) & 0xF; + + usedPaletteIndices[lower] = true; + usedPaletteIndices[upper] = true; + } + + paletteReversed.clear(); + + // default biome + paletteReversed.put(palette[0], 0); + + for (int i = 1; i < 16; i++) { + if (!usedPaletteIndices[i]) { + 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/visibility/CuboidalCubeSelector.java b/src/main/java/com/cardinalstar/cubicchunks/visibility/CuboidalCubeSelector.java index fc21ba1c..361e40d6 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..b42479d8 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,20 @@ 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 +66,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 +76,59 @@ 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/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/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/chunkloader/CubicChunkManager.java b/src/main/java/com/cardinalstar/cubicchunks/world/chunkloader/CubicChunkManager.java index 492f8e95..a902ef0d 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; @@ -235,47 +229,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/core/IColumnInternal.java b/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java index fbe5fb76..be8aa779 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/core/IColumnInternal.java @@ -32,6 +32,8 @@ public interface IColumnInternal extends IColumn { + void setColumn(boolean isColumn); + Block[] getCompatGenerationBlockArray(); byte[] getCompatGenerationByteArray(); 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..3a0359e5 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,9 +28,13 @@ 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 +718,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 1dfe3399..d1b56832 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/cube/Cube.java @@ -57,26 +57,32 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage; 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; import com.cardinalstar.cubicchunks.api.ICube; import com.cardinalstar.cubicchunks.api.IHeightMap; -import com.cardinalstar.cubicchunks.api.worldgen.ICubeGenerator; +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.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; 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 * with a side length of {@link Cube#SIZE}, aligned to multiples of that length and stored within columns. @@ -91,7 +97,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 @@ -100,15 +106,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 +315,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,47 +473,50 @@ public void tickCubeCommon(BooleanSupplier tryToTickFaster) { * @param rand - World specific Random */ public void tickCubeServer(BooleanSupplier tryToTickFaster, Random rand) { - if (!isFullyPopulated) { + if (!isFullyPopulated()) { return; } 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); } } // ================================= @@ -591,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; @@ -669,7 +673,16 @@ 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)); + + 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") @@ -698,6 +711,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 +739,6 @@ public void onCubeUnload() { this.world.func_147457_a(tileEntity); } ((IColumnInternal) getColumn()).removeFromStagingHeightmap(this); - EVENT_BUS.post(new CubeEvent.Unload(this)); } @Override @@ -783,44 +796,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; } /** @@ -880,19 +882,28 @@ 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))) { + if (this.tickets.anyMatch(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/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/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..5182e972 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/savedata/WorldFormatSavedData.java @@ -0,0 +1,63 @@ +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 new file mode 100644 index 00000000..155a36cb --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/FeatureCubicGenerator.java @@ -0,0 +1,107 @@ +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.IWorldGenerator; +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..b6fafb9c --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCaveFluids.java @@ -0,0 +1,91 @@ +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.decoration.ICubePopulator; +import com.cardinalstar.cubicchunks.util.XSTR; +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; + +public class MapGenCaveFluids implements ICubePopulator { + + 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 populate(World world, Cube cube) { + if (cube.getY() >= 0) return; + + IMutableBlockView cubeView = new CubeBlockView(cube); + + long seed = Fnv1a64.initialState(); + + seed = Fnv1a64.hashStep(seed, world.getSeed()); + seed = Fnv1a64.hashStep(seed, cube.getX()); + seed = Fnv1a64.hashStep(seed, cube.getY()); + seed = Fnv1a64.hashStep(seed, cube.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 + cube.getX() * 16; + int globalY = y + cube.getY() * 16; + int globalZ = z + cube.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 e781e274..521f144e 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/MapGenCavesCubic.java @@ -1,35 +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 com.cardinalstar.cubicchunks.worldgen.VanillaWorldGenerator; +import com.gtnewhorizon.gtnhlib.util.data.ImmutableBlockMeta; +import com.gtnewhorizon.gtnhlib.util.data.LazyBlock; -import it.unimi.dsi.fastutil.longs.LongIterator; -import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; - -public class MapGenCavesCubic - extends SeedBasedCubicPopulator { +public class MapGenCavesCubic extends FeatureCubicGenerator { private final XSTR caveRandom = new XSTR(0); @@ -38,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); @@ -79,35 +67,37 @@ protected void getSeeds(Random rng, int cubeX, int cubeY, int cubeZ, List getSeedBranches(CaveSeed caveSeed) { + return caveSeed.branches; } - protected void generateCave(CaveSeed caveSeed, WorldView worldView) { + @Override + protected boolean shouldGenerate(WorldView worldView, WorldgenFeature feature) { Box box = worldView.getBounds(); - if (!scanOuterBoxForWater( + return !scanOuterBoxForWater( worldView, box.getX1(), box.getX2(), box.getY1(), box.getY2(), box.getZ1(), - box.getZ2())) { - CubePos pos = worldView.getCube(); - - for (Vector3ic v : caveSeed.getDigs(pos.getX(), pos.getY(), pos.getZ())) { - int x = v.x() + box.getX1(); - int y = v.y() + box.getY1(); - int z = v.z() + box.getZ1(); - - digBlock(worldView, x, y, z); - } - } - - for (CaveSeed branch : caveSeed.branches) { - generateCave(branch, worldView); - } + box.getZ2()); } private static boolean scanOuterBoxForWater(WorldView worldView, int xmin, int xmax, int zmin, int zmax, int ymax, @@ -149,18 +139,9 @@ 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); @@ -168,15 +149,14 @@ protected void digBlock(WorldView worldView, int x, int y, int z) { 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); - } + 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; @@ -287,7 +267,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); } } } @@ -301,7 +281,7 @@ protected void walkCave(CaveSeed cave) { } } - protected class CaveSeed { + protected static class CaveSeed { private final long seed; private final double offsetX; @@ -314,10 +294,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, @@ -332,96 +308,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 89% 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 b544ce35..42b0c8de 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicPopulator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/SeedBasedCubicGenerator.java @@ -8,17 +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 SeedBasedCubicPopulator - implements ICubeTerrainGenerator { +public abstract class SeedBasedCubicGenerator implements ICubeGenerator { private final XSTR rng = new XSTR(0); @@ -46,7 +47,7 @@ public abstract class SeedBasedCubicPopulator= 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 new file mode 100644 index 00000000..102187cc --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldGenerators.java @@ -0,0 +1,74 @@ +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.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; + +public class WorldGenerators { + + private static final LazyBlock WATER_STILL = new LazyBlock(Mods.Minecraft, () -> Blocks.water); + private static final LazyBlock LAVA_STILL = new LazyBlock(Mods.Minecraft, () -> Blocks.lava); + + public static void init() { + initVanillaTerrain(); + initVanillaPopulation(); + + if (Mods.EtFuturumRequiem.isModLoaded()) { + initEFRPopulation(); + } + } + + 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() + .addTarget("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)); + } + + private static void initVanillaPopulation() { + // CUBIC_VANILLA.population() + // .addObject("biomes", new CaveBiomePopulator()); + } + + @Optional.Method(modid = Mods.ModIDs.ET_FUTURUM_REQUIEM) + private static void initEFRPopulation() { + CUBIC_VANILLA.population() + .addObject("low-deepslate", new DeepslateCubePopulator(), "requires:biomes"); + } + + 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/WorldgenFeature.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java new file mode 100644 index 00000000..42f83edf --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/WorldgenFeature.java @@ -0,0 +1,144 @@ +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/world/worldgen/caves/NoodleCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/NoodleCaveGenerator.java new file mode 100644 index 00000000..9e03bf6e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/NoodleCaveGenerator.java @@ -0,0 +1,84 @@ +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/caves/SpaghettiCaveGenerator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/SpaghettiCaveGenerator.java new file mode 100644 index 00000000..76d4f4d3 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/caves/SpaghettiCaveGenerator.java @@ -0,0 +1,75 @@ +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/modcompat/efr/DeepslateCubePopulator.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/compat/DeepslateCubePopulator.java similarity index 54% 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 638da5a4..c48be45f 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; @@ -6,10 +6,8 @@ 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; @@ -18,17 +16,19 @@ 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; - 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()); + IMutableBlockView blocks = new CubeBlockView(cube); + + 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 +42,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 71% 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 86de5b03..0e55fb62 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; @@ -8,6 +8,7 @@ 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; @@ -16,44 +17,25 @@ 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 +83,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; } 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..307839ed --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/data/NoisePrecalculator.java @@ -0,0 +1,187 @@ +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; + +import org.jetbrains.annotations.NotNull; + +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.ObjectPooler; +import com.cardinalstar.cubicchunks.util.XSTR; +import com.cardinalstar.cubicchunks.world.worldgen.noise.NoiseSampler; +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> { + + 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; + 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) { + 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) { + TaskKey task = new TaskKey(getSamplers(world), world.provider.dimensionId, cubeX, cubeY, cubeZ); + + TaskPool.submit(this.noiseTaskExecutor, task); + } + + public NoiseData takeSampler(World world, int cubeX, int cubeY, int cubeZ) { + TaskKey key = new TaskKey(null, 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); + } + } + } + }); + } + + @Data + private class TaskKey { + + @EqualsAndHashCode.Exclude + private final EnumMap samplers; + private final int worldId; + private final int x; + private final int y; + private final 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/noise/BlockNoiseSampler.java b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java new file mode 100644 index 00000000..5998b1fd --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/BlockNoiseSampler.java @@ -0,0 +1,9 @@ +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..a18a9a9e --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/NoiseSampler.java @@ -0,0 +1,9 @@ +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..ea0e87f9 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/noise/SimplexNoiseSampler.java @@ -0,0 +1,185 @@ +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..cf2971f4 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/world/worldgen/vanilla/PrecalcedVanillaOctaves.java @@ -0,0 +1,262 @@ +package com.cardinalstar.cubicchunks.world.worldgen.vanilla; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import net.minecraft.world.gen.NoiseGeneratorOctaves; + +import com.cardinalstar.cubicchunks.CubicChunks; +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 it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + +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; + 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 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; + + 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(); + + 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); + } + } + + @Override + public boolean canMerge(List> tasks, Task3D task3D) { + return tasks.size() < 32; + } + }; + } + + 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: {} Pres: {} Per call: {}ms", + hits, + misses2, + pres.getAndSet(0), + (elapsed / (double) misses2 / 1e6)); + hits = 0; + misses2 = 0; + elapsed = 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 (cache) { + cached = cache.remove(Coords.key(blockX, blockY, blockZ)); + } + + if (cached != null && cached.matches()) { + System.arraycopy(cached.data, 0, data, 0, cached.data.length); + + hits++; + return data; + } + + misses2++; + + 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() { + 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(noiseTaskExecutor, task); + } + + @Desugar + + private record TaskKey(int x, int y, int z) { + + } + + private class Task3D { + + 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; + } + + public NoiseData run() { + 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; + + base.generateNoiseOctaves(data.data, key.x, key.y, key.z, xspan, yspan, zspan, xscale, yscale, zscale); + + return data; + } + } + + 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 51% rename from src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java rename to src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java index 65b99849..5c3b3020 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaCompatibilityGenerator.java +++ b/src/main/java/com/cardinalstar/cubicchunks/worldgen/VanillaWorldGenerator.java @@ -20,58 +20,65 @@ */ package com.cardinalstar.cubicchunks.worldgen; +import java.util.ArrayList; 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; 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 +86,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 +109,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() { @@ -132,6 +131,8 @@ private FillerInfo getBottomFillerInfo() { Chunk chunk = vanilla.provideChunk(0, 0); + ((IColumnInternal) chunk).setColumn(false); + bottom = analyzeBottomFiller(new ChunkBlockView(chunk)); return bottom; @@ -175,6 +176,8 @@ private FillerInfo getTopFillerInfo() { Chunk chunk = vanilla.provideChunk(0, 0); + ((IColumnInternal) chunk).setColumn(false); + top = analyzeTopFiller(new ChunkBlockView(chunk)); return top; @@ -209,140 +212,166 @@ private FillerInfo analyzeTopFiller(IBlockView blockView) { } @Override - public void generateColumn(Chunk column) { + public void onColumnPreloadFailed(ChunkCoordIntPair pos) { + if (vanilla instanceof Precalculable precalc) { + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { + precalc.precalculate(pos.chunkXPos + dx, 0, pos.chunkZPos + dz); + } + } + } + } + + @Override + public GenerationResult provideColumn(World world, int columnX, int columnZ) { + Pair data = getVanillaChunkView(columnX, columnZ); + + List cubes = new ArrayList<>(); - this.biomes = this.world.getWorldChunkManager() - .loadBlockGeneratorData(this.biomes, column.xPosition * 16, column.zPosition * 16, 16, 16); + // Ceiling div by 16 + int heightCubes = (data.right() + .getBounds() + .getSizeY() + 15) >> 4; - byte[] biomeArray = column.getBiomeArray(); - for (int i = 0; i < biomeArray.length; ++i) { - biomeArray[i] = (byte) this.biomes[i].biomeID; + 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 + 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 Cube 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; + List generatedColumns = new ArrayList<>(); + List generatedCubes = new ArrayList<>(); - if (cubeY < 0) { - FillerInfo fillerInfo = getBottomFillerInfo(); + if (cubeY >= 0 && cubeY < 16 || chunk == null) { + // Generate the vanilla chunk + Pair data = getVanillaChunkView(cubeX, cubeZ); - Block[] blocks = new Block[4096]; - int[] blockMeta = new int[4096]; + IBlockView chunkBlocks = data.right(); - cubeData = new ChunkArrayBlockView( - 16, - 16, - 16, - ObjectArrayList.wrap(blocks), - IntArrayList.wrap(blockMeta)); + 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); + } - // Fill with bottom block - ((IMutableBlockView) cubeData).fill(fillerInfo.filler); - } else if (cubeY >= worldHeightCubes) { - FillerInfo fillerInfo = getTopFillerInfo(); + // Ceiling div by 16 + int heightCubes = (chunkBlocks.getBounds() + .getSizeY() + 15) >> 4; - Block[] blocks = new Block[4096]; - int[] blockMeta = new int[4096]; + for (int y = 0; y < heightCubes; y++) { + Cube c = new Cube(chunk, y, chunkBlocks.subView(Box.horizontalChunkSlice(y << 4, 16))); - cubeData = new ChunkArrayBlockView( - 16, - 16, - 16, - ObjectArrayList.wrap(blocks), - IntArrayList.wrap(blockMeta)); + try { + decorator.generate(world, c); + } catch (Throwable t) { + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, y, cubeZ, t); + } - // Fill with top block - ((IMutableBlockView) cubeData).fill(fillerInfo.filler); - } else { - cubeData = getVanillaChunkSlice(cubeX, cubeY, cubeZ); + generatedCubes.add(c); + } } - Cube cube = new Cube(chunk, cubeY, cubeData); + if (cubeY < 0 || cubeY >= 16) { + FillerInfo fillerInfo = cubeY < 0 ? getBottomFillerInfo() : getTopFillerInfo(); + IBlockView cubeData = new UniformBlockView(fillerInfo.filler); - try { - CubeGeneratorsRegistry.generateVanillaCube(this, world, cube); - } catch (Throwable t) { - CubicChunks.LOGGER - .error("Could not run non-vanilla generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); - } + Cube cube = new Cube(chunk, cubeY, cubeData); - return cube; - } finally { - WorldgenHangWatchdog.endWorldGen(); - } - } + try { + decorator.generate(world, cube); + } catch (Throwable t) { + CubicChunks.LOGGER.error("Could not run generation for cube {},{},{}", cubeX, cubeY, cubeZ, t); + } - 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); + generatedCubes.add(cube); + } - XSTR rand = new XSTR(hash); + Cube primary = null; - generateVanillaChunk(cubeX, cubeZ, rand); - } + for (int i = 0; i < generatedCubes.size(); i++) { + Cube c = generatedCubes.get(i); - // 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; + if (c.getY() == cubeY) { + primary = c; + generatedCubes.remove(i); + break; } - - getCubeLoader().getCube(cubeX, y, cubeZ, ICubeProviderServer.Requirement.GENERATE); } - optimizationHack = false; - } - return lastChunkView.subView(Box.horizontalChunkSlice(cubeY * 16, 16)); + return new GenerationResult<>(primary, generatedColumns, generatedCubes); + } finally { + WorldgenHangWatchdog.endWorldGen(); + } } - 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); - - lastChunkView = new ChunkBlockView(lastChunk); + Chunk chunk = vanilla.provideChunk(cubeX, cubeZ); - removeBedrock(lastChunkView, rand); + return Pair.of(chunk, new SafeMutableBlockView(Box.horizontalChunkSlice(0, 256), new ChunkBlockView(chunk))); } private static Int2ObjectFunction wrapBlockArray(Block[] compatBlocks) { @@ -387,127 +416,118 @@ 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(); - for (Vector3ic v : BOTTOM_BEDROCK_LAYER) { - if (chunk.getBlock(v.x(), v.y(), v.z()) == Blocks.bedrock) { - boolean isBottomLayer = v.y() == 0; + int cx = cube.getX(); + int cy = cube.getY(); + int cz = cube.getZ(); - // 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); - } - } - } + try { + WorldgenHangWatchdog.startWorldGen(); - for (Vector3ic v : topBedrockLayer) { - if (chunk.getBlock(v.x(), v.y(), v.z()) == Blocks.bedrock) { - boolean isTopLayer = v.y() == worldHeightBlocks - 1; + // 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 top layer because it's too dense - if (rand.nextInt(isTopLayer ? 2 : 3) == 0) { - chunk.setBlock(v.x(), v.y(), v.z(), top.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(); + } } } - } - } - @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(); + if (!center.isPopulated()) { + decorator.populate(world, center); - try { - CubeGeneratorsRegistry.populateVanillaCubic(world, cube); - } catch (Throwable t) { - CubicChunks.LOGGER.error( - "Could not run non-vanilla population for cube {},{},{}", - cube.getX(), - cube.getY(), - cube.getZ(), - t); - } + 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 + // 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). - Cube withinVanillaChunk = cube; + populateChunk(loader, v.x(), v.z()); + } - // 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_000); + } } - // Populate the vanilla chunk if it isn't already - if (withinVanillaChunk != null && !withinVanillaChunk.isFullyPopulated()) - populateChunk(getCubeLoader(), cube); + for (Vector3ic v : getFullyPopulatedCubes(cx, cy, cz)) { + Cube center = loader.getCube(v.x(), v.y(), v.z(), Requirement.GENERATE); - // 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); + center.markPopulated(Cube.POP_ALL); + loader.onCubeGenerated(center); + } + } 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, z - 1, x, 15, z); + } else { + return new Box(x - 1, y - 1, z - 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); - } + column.isTerrainPopulated = true; + column.isModified = true; try { CompatHandler.beforePopulate(world, vanilla); - // Then we can populate this cube - vanilla.populate(vanilla, cube.getX(), cube.getZ()); + vanilla.populate(vanilla, columnX, columnZ); - GameRegistry.generateWorld(cube.getX(), cube.getZ(), world, vanilla, world.getChunkProvider()); - - 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(); @@ -519,7 +539,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); @@ -531,7 +551,28 @@ private void applyModGenerators(int x, int z, World world, IChunkProvider vanill } @Override - public void recreateStructures(ICube cube) {} + 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) { + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { + precalc.precalculate(pos.getX() + dx, 0, pos.getZ() + dz); + } + } + } + + decorator.pregenerate(world, pos); + } + + if (populate) { + decorator.prepopulate(world, pos); + } + } @Override public List getPossibleCreatures(EnumCreatureType creatureType, int x, int y, int z) { diff --git a/src/main/resources/assets/cubicchunks/lang/en_US.lang b/src/main/resources/assets/cubicchunks/lang/en_US.lang index 4786ef09..e3b55d7b 100644 --- a/src/main/resources/assets/cubicchunks/lang/en_US.lang +++ b/src/main/resources/assets/cubicchunks/lang/en_US.lang @@ -25,3 +25,9 @@ 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 + +generator.VanillaCubic=Vanilla + Cubic 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"); + } + } +}