From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Mon, 26 Dec 2022 11:25:35 -0500 Subject: [PATCH] AdvancedSlimePaper Server Changes AdvancedSlimePaper Copyright (C) 2023 InfernalSuite This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/src/main/java/com/infernalsuite/aswm/Converter.java b/src/main/java/com/infernalsuite/aswm/Converter.java new file mode 100644 index 0000000000000000000000000000000000000000..7d1753c0b7e89bbf0c245a0231b62773eca2779e --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/Converter.java @@ -0,0 +1,118 @@ +package com.infernalsuite.aswm; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.TagType; +import com.infernalsuite.aswm.api.utils.NibbleArray; +import net.minecraft.nbt.*; +import net.minecraft.world.level.chunk.DataLayer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class Converter { + + private static final Logger LOGGER = LogManager.getLogger("SWM Converter"); + + static DataLayer convertArray(NibbleArray array) { + return new DataLayer(array.getBacking()); + } + + public static NibbleArray convertArray(DataLayer array) { + if(array == null) { + return null; + } + + return new NibbleArray(array.getData()); + } + + public static Tag convertTag(com.flowpowered.nbt.Tag tag) { + try { + switch(tag.getType()) { + case TAG_BYTE: + return ByteTag.valueOf(((com.flowpowered.nbt.ByteTag) tag).getValue()); + case TAG_SHORT: + return ShortTag.valueOf(((com.flowpowered.nbt.ShortTag) tag).getValue()); + case TAG_INT: + return IntTag.valueOf(((com.flowpowered.nbt.IntTag) tag).getValue()); + case TAG_LONG: + return LongTag.valueOf(((com.flowpowered.nbt.LongTag) tag).getValue()); + case TAG_FLOAT: + return FloatTag.valueOf(((com.flowpowered.nbt.FloatTag) tag).getValue()); + case TAG_DOUBLE: + return DoubleTag.valueOf(((com.flowpowered.nbt.DoubleTag) tag).getValue()); + case TAG_BYTE_ARRAY: + return new ByteArrayTag(((com.flowpowered.nbt.ByteArrayTag) tag).getValue()); + case TAG_STRING: + return StringTag.valueOf(((com.flowpowered.nbt.StringTag) tag).getValue()); + case TAG_LIST: + ListTag list = new ListTag(); + ((com.flowpowered.nbt.ListTag) tag).getValue().stream().map(Converter::convertTag).forEach(list::add); + + return list; + case TAG_COMPOUND: + CompoundTag compound = new CompoundTag(); + + ((com.flowpowered.nbt.CompoundTag) tag).getValue().forEach((key, value) -> compound.put(key, convertTag(value))); + return compound; + case TAG_INT_ARRAY: + return new IntArrayTag(((com.flowpowered.nbt.IntArrayTag) tag).getValue()); + case TAG_LONG_ARRAY: + return new LongArrayTag(((com.flowpowered.nbt.LongArrayTag) tag).getValue()); + default: + throw new IllegalArgumentException("Invalid tag type " + tag.getType().name()); + } + } catch(Exception ex) { + LOGGER.error("Failed to convert NBT object:"); + LOGGER.error(tag.toString()); + + throw ex; + } + } + + public static com.flowpowered.nbt.Tag convertTag(String name, Tag base) { + switch(base.getId()) { + case Tag.TAG_BYTE: + return new com.flowpowered.nbt.ByteTag(name, ((ByteTag) base).getAsByte()); + case Tag.TAG_SHORT: + return new com.flowpowered.nbt.ShortTag(name, ((ShortTag) base).getAsShort()); + case Tag.TAG_INT: + return new com.flowpowered.nbt.IntTag(name, ((IntTag) base).getAsInt()); + case Tag.TAG_LONG: + return new com.flowpowered.nbt.LongTag(name, ((LongTag) base).getAsLong()); + case Tag.TAG_FLOAT: + return new com.flowpowered.nbt.FloatTag(name, ((FloatTag) base).getAsFloat()); + case Tag.TAG_DOUBLE: + return new com.flowpowered.nbt.DoubleTag(name, ((DoubleTag) base).getAsDouble()); + case Tag.TAG_BYTE_ARRAY: + return new com.flowpowered.nbt.ByteArrayTag(name, ((ByteArrayTag) base).getAsByteArray()); + case Tag.TAG_STRING: + return new com.flowpowered.nbt.StringTag(name, ((StringTag) base).getAsString()); + case Tag.TAG_LIST: + List list = new ArrayList<>(); + ListTag originalList = ((ListTag) base); + + for(Tag entry : originalList) { + list.add(convertTag("", entry)); + } + + return new com.flowpowered.nbt.ListTag(name, TagType.getById(originalList.getElementType()), list); + case Tag.TAG_COMPOUND: + CompoundTag originalCompound = ((CompoundTag) base); + com.flowpowered.nbt.CompoundTag compound = new com.flowpowered.nbt.CompoundTag(name, new CompoundMap()); + + for(String key : originalCompound.getAllKeys()) { + compound.getValue().put(key, convertTag(key, originalCompound.get(key))); + } + + return compound; + case Tag.TAG_INT_ARRAY: + return new com.flowpowered.nbt.IntArrayTag(name, ((IntArrayTag) base).getAsIntArray()); + case Tag.TAG_LONG_ARRAY: + return new com.flowpowered.nbt.LongArrayTag(name, ((LongArrayTag) base).getAsLongArray()); + default: + throw new IllegalArgumentException("Invalid tag type " + base.getId()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/InternalPlugin.java b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..61518ab2b68e7a41500f3c8c8a5ec1230597f0e5 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java @@ -0,0 +1,28 @@ +package com.infernalsuite.aswm; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.Server; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.plugin.PluginLogger; +import org.jetbrains.annotations.NotNull; + +import java.util.logging.LogRecord; + +public class InternalPlugin extends MinecraftInternalPlugin { + + @Override + public @NotNull Server getServer() { + return MinecraftServer.getServer().server; + } + + @Override + public @NotNull PluginLogger getLogger() { + return new PluginLogger(new InternalPlugin()) { + @Override + public void log(@NotNull LogRecord logRecord) { + MinecraftServer.LOGGER.info(logRecord.getMessage()); + } + }; + } + +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..967523e3b2913a57d5f6ee443142d71e96d3fe00 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java @@ -0,0 +1,104 @@ +package com.infernalsuite.aswm; + +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; +import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; +import com.flowpowered.nbt.CompoundTag; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeChunkSection; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.serialization.SlimeWorldReader; +import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; +import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton; +import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton; +import net.minecraft.SharedConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +class SimpleDataFixerConverter implements SlimeWorldReader { + + @Override + public SlimeWorld readFromData(SlimeWorld data) { + int newVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + int currentVersion = data.getDataVersion(); + // Already fixed + if (currentVersion == newVersion) { + return data; + } + + Map chunks = new HashMap<>(); + for (SlimeChunk chunk : data.getChunkStorage()) { + List entities = new ArrayList<>(); + List blockEntities = new ArrayList<>(); + for (CompoundTag upgradeEntity : chunk.getTileEntities()) { + blockEntities.add( + convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.TILE_ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion)) + ); + } + for (CompoundTag upgradeEntity : chunk.getEntities()) { + entities.add( + convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion)) + ); + } + + ChunkPos chunkPos = new ChunkPos(chunk.getX(), chunk.getZ()); + + SlimeChunkSection[] sections = new SlimeChunkSection[chunk.getSections().length]; + for (int i = 0; i < sections.length; i++) { + SlimeChunkSection dataSection = chunk.getSections()[i]; + if (dataSection == null) continue; + + CompoundTag blockStateTag = blockStateTag = convertAndBack(dataSection.getBlockStatesTag(), (tag) -> { + WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, new NBTMapType(tag), "palette", currentVersion, newVersion); + }); + + CompoundTag biomeTag = convertAndBack(dataSection.getBiomeTag(), (tag) -> { + WalkerUtils.convertList(MCTypeRegistry.BIOME, new NBTMapType(tag), "palette", currentVersion, newVersion); + }); + + sections[i] = new SlimeChunkSectionSkeleton( + blockStateTag, + biomeTag, + dataSection.getBlockLight(), + dataSection.getSkyLight() + ); + } + + chunks.put(chunkPos, new SlimeChunkSkeleton( + chunk.getX(), + chunk.getX(), + sections, + chunk.getHeightMaps(), + blockEntities, + entities + )); + + } + + return new SkeletonSlimeWorld( + data.getName(), + data.getLoader(), + data.isReadOnly(), + chunks, + data.getExtraData(), + data.getPropertyMap(), + newVersion + ); + } + + + private static CompoundTag convertAndBack(CompoundTag value, Consumer acceptor) { + if (value == null) { + return null; + } + + net.minecraft.nbt.CompoundTag converted = (net.minecraft.nbt.CompoundTag) Converter.convertTag(value); + acceptor.accept(converted); + + return (CompoundTag) Converter.convertTag(value.getName(), converted); + } +} diff --git a/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..2cb9ae2314fd3bdbbf8c7e161cd7b9300ab580d8 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java @@ -0,0 +1,211 @@ +package com.infernalsuite.aswm; + +import com.infernalsuite.aswm.api.SlimeNMSBridge; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.SlimeWorldInstance; +import com.infernalsuite.aswm.api.world.properties.SlimeProperties; +import com.infernalsuite.aswm.level.SlimeBootstrap; +import com.infernalsuite.aswm.level.SlimeInMemoryWorld; +import com.infernalsuite.aswm.level.SlimeLevelInstance; +import com.mojang.serialization.Lifecycle; +import net.minecraft.SharedConstants; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.DedicatedServerProperties; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.storage.CommandStorage; +import net.minecraft.world.level.storage.DimensionDataStorage; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Locale; + +public class SlimeNMSBridgeImpl implements SlimeNMSBridge { + + private static final SimpleDataFixerConverter DATA_FIXER_CONVERTER = new SimpleDataFixerConverter(); + + private static final Logger LOGGER = LogManager.getLogger("SWM"); + + private SlimeWorld defaultWorld; + private SlimeWorld defaultNetherWorld; + private SlimeWorld defaultEndWorld; + + public static SlimeNMSBridgeImpl instance() { + return (SlimeNMSBridgeImpl) SlimeNMSBridge.instance(); + } + + @Override + public boolean loadOverworldOverride() { + if (defaultWorld == null) { + return false; + } + + // See MinecraftServer loading logic + // Some stuff is needed when loading overworld world + SlimeLevelInstance instance = ((SlimeInMemoryWorld) this.loadInstance(defaultWorld, Level.OVERWORLD)).getInstance(); + DimensionDataStorage worldpersistentdata = instance.getDataStorage(); + instance.getCraftServer().scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(instance.getServer(), instance.getScoreboard()); + instance.getServer().commandStorage = new CommandStorage(worldpersistentdata); + + return true; + } + + @Override + public boolean loadNetherOverride() { + if (defaultNetherWorld == null) { + return false; + } + + this.loadInstance(defaultNetherWorld, Level.NETHER); + + return true; + } + + @Override + public boolean loadEndOverride() { + if (defaultEndWorld == null) { + return false; + } + + this.loadInstance(defaultEndWorld, Level.END); + + return true; + } + + @Override + public void setDefaultWorlds(SlimeWorld normalWorld, SlimeWorld netherWorld, SlimeWorld endWorld) { + if (normalWorld != null) { + normalWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NORMAL.toString().toLowerCase()); + defaultWorld = normalWorld; + } + + if (netherWorld != null) { + netherWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NETHER.toString().toLowerCase()); + defaultNetherWorld = netherWorld; + } + + if (endWorld != null) { + endWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.THE_END.toString().toLowerCase()); + defaultEndWorld = endWorld; + } + + } + + @Override + public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld) { + return this.loadInstance(slimeWorld, null); + } + + public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld, @Nullable ResourceKey dimensionOverride) { + String worldName = slimeWorld.getName(); + + if (Bukkit.getWorld(worldName) != null) { + throw new IllegalArgumentException("World " + worldName + " already exists! Maybe it's an outdated SlimeWorld object?"); + } + + SlimeLevelInstance server = createCustomWorld(slimeWorld, dimensionOverride); + registerWorld(server); + return server.getSlimeInstance(); + } + + @Override + public SlimeWorldInstance getInstance(World world) { + CraftWorld craftWorld = (CraftWorld) world; + + if (!(craftWorld.getHandle() instanceof SlimeLevelInstance worldServer)) { + return null; + } + + return worldServer.getSlimeInstance(); + } + + @Override + public SlimeWorld applyDataFixers(SlimeWorld world) { + return DATA_FIXER_CONVERTER.readFromData(world); + } + + + @Override + public int getCurrentVersion() { + return SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + } + + public void registerWorld(SlimeLevelInstance server) { + MinecraftServer mcServer = MinecraftServer.getServer(); + mcServer.initWorld(server, server.serverLevelData, mcServer.getWorldData(), server.serverLevelData.worldGenOptions()); + + mcServer.addLevel(server); + } + + private SlimeLevelInstance createCustomWorld(SlimeWorld world, @Nullable ResourceKey dimensionOverride) { + SlimeBootstrap bootstrap = new SlimeBootstrap(world); + String worldName = world.getName(); + + PrimaryLevelData worldDataServer = createWorldData(world); + World.Environment environment = getEnvironment(world); + ResourceKey dimension = switch (environment) { + case NORMAL -> LevelStem.OVERWORLD; + case NETHER -> LevelStem.NETHER; + case THE_END -> LevelStem.END; + default -> throw new IllegalArgumentException("Unknown dimension supplied"); + }; + + ResourceKey worldKey = dimensionOverride == null ? ResourceKey.create(Registries.DIMENSION, new ResourceLocation(worldName.toLowerCase(Locale.ENGLISH))) : dimensionOverride; + LevelStem stem = MinecraftServer.getServer().registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM).get(dimension); + + SlimeLevelInstance level; + + try { + level = new SlimeLevelInstance(bootstrap, worldDataServer, worldKey, dimension, stem, environment); + } catch (IOException ex) { + throw new RuntimeException(ex); // TODO do something better with this? + } + + // level.setReady(true); + level.setSpawnSettings(world.getPropertyMap().getValue(SlimeProperties.ALLOW_MONSTERS), world.getPropertyMap().getValue(SlimeProperties.ALLOW_ANIMALS)); + + var nmsExtraData = (CompoundTag) Converter.convertTag(world.getExtraData()); + + //Attempt to read PDC + if (nmsExtraData.get("BukkitValues") != null) level.getWorld().readBukkitValues(nmsExtraData.get("BukkitValues")); + + return level; + } + + private World.Environment getEnvironment(SlimeWorld world) { + return World.Environment.valueOf(world.getPropertyMap().getValue(SlimeProperties.ENVIRONMENT).toUpperCase()); + } + + private PrimaryLevelData createWorldData(SlimeWorld world) { + MinecraftServer mcServer = MinecraftServer.getServer(); + DedicatedServerProperties serverProps = ((DedicatedServer) mcServer).getProperties(); + String worldName = world.getName(); + + LevelSettings worldsettings = new LevelSettings(worldName, serverProps.gamemode, false, serverProps.difficulty, + true, new GameRules(), mcServer.worldLoader.dataConfiguration()); + + WorldOptions worldoptions = new WorldOptions(0, false, false); + + PrimaryLevelData data = new PrimaryLevelData(worldsettings, worldoptions, PrimaryLevelData.SpecialWorldProperty.FLAT, Lifecycle.stable()); + data.checkName(worldName); + data.setModdedInfo(mcServer.getServerModName(), mcServer.getModdedStatus().shouldReportAsModified()); + data.setInitialized(true); + + return data; + } + +} diff --git a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java new file mode 100644 index 0000000000000000000000000000000000000000..65642a9bde0fffc6531604026dd957fd268b6642 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java @@ -0,0 +1,117 @@ +package com.infernalsuite.aswm.level; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import com.infernalsuite.aswm.Converter; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.mojang.logging.LogUtils; +import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask; +import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler; +import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ImposterProtoChunk; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.LevelChunkTicks; +import org.slf4j.Logger; + +import java.util.function.Consumer; + +public final class ChunkDataLoadTask implements CommonLoadTask { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + + private final ChunkTaskScheduler scheduler; + private final ServerLevel world; + private final int chunkX; + private final int chunkZ; + private Consumer> onRun; + + private PrioritisedExecutor.PrioritisedTask task; + + private final ChunkLoadTask chunkLoadTask; + + protected ChunkDataLoadTask(ChunkLoadTask chunkLoadTask, final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, + final int chunkZ, final PrioritisedExecutor.Priority priority, final Consumer> onRun) { + this.chunkLoadTask = chunkLoadTask; + this.scheduler = scheduler; + this.world = world; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.onRun = onRun; + + this.task = this.scheduler.createChunkTask(this.chunkX, this.chunkZ, () -> { + try { + SlimeChunk chunk = ((SlimeLevelInstance) this.world).slimeInstance.getChunk(this.chunkX, this.chunkZ); + this.onRun.accept(new GenericDataLoadTask.TaskResult<>(runOnMain(chunk), null)); + } catch (final Exception e) { + LOGGER.error("ERROR", e); + this.onRun.accept(new GenericDataLoadTask.TaskResult<>(null, e)); + } + }, priority); + } + + private ChunkAccess getEmptyChunk() { + LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); + LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); + + return new ImposterProtoChunk(new LevelChunk(this.world, new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, + 0L, null, chunk -> {}, null), true); + } + + protected ChunkAccess runOnMain(final SlimeChunk data) { + // have tasks to run (at this point, it's just the POI consistency checking) + try { + // if (data.tasks != null) { + // for (int i = 0, len = data.tasks.size(); i < len; i) { + // data.tasks.poll().run(); + // } + // } + + LevelChunk chunk = this.world.slimeInstance.promote(chunkX, chunkZ, data); + ImposterProtoChunk protoChunk = new ImposterProtoChunk(chunk, false); + if (data != null) { + data.getEntities().stream().map(flowTag -> (CompoundTag) Converter.convertTag(flowTag)).forEach(protoChunk::addEntity); + } + + return new ImposterProtoChunk(chunk, false); + } catch (final Exception e) { + LOGGER.error("Failed to parse main tasks for task {}, chunk data will be lost", this, e); + return this.getEmptyChunk(); + } + } + + @Override + public PrioritisedExecutor.Priority getPriority() { + return this.task.getPriority(); + } + + @Override + public void setPriority(PrioritisedExecutor.Priority priority) { + this.task.setPriority(priority); + } + + @Override + public void raisePriority(PrioritisedExecutor.Priority priority) { + this.task.raisePriority(priority); + } + + @Override + public void lowerPriority(PrioritisedExecutor.Priority priority) { + this.task.lowerPriority(priority); + } + + @Override + public boolean cancel() { + return this.task.cancel(); + } + + public boolean schedule(boolean schedule) { + this.scheduler.scheduleChunkTask(chunkX, chunkZ, this.task::execute); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java new file mode 100644 index 0000000000000000000000000000000000000000..fc6e46972bcc77134ed718c8c157ec3893d4bcdf --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java @@ -0,0 +1,18 @@ +package com.infernalsuite.aswm.level; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; + +public interface CommonLoadTask { + + boolean schedule(boolean schedule); + + PrioritisedExecutor.Priority getPriority(); + + boolean cancel(); + + void lowerPriority(PrioritisedExecutor.Priority priority); + + void raisePriority(PrioritisedExecutor.Priority priority); + + void setPriority(PrioritisedExecutor.Priority priority); +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java new file mode 100644 index 0000000000000000000000000000000000000000..c0e47f25e9be33da374dc737c96d8d3c2bb1cd0f --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java @@ -0,0 +1,59 @@ +package com.infernalsuite.aswm.level; + +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.properties.SlimeProperties; +import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import io.papermc.paper.world.ChunkEntitySlices; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; + +public class FastChunkPruner { + + public static boolean canBePruned(SlimeWorld world, LevelChunk chunk) { + // Kenox + // It's not safe to assume that the chunk can be pruned + // if there isn't a loaded chunk there + if (chunk == null || chunk.getChunkHolder() == null) { + return false; + } + + SlimePropertyMap propertyMap = world.getPropertyMap(); + if (propertyMap.getValue(SlimeProperties.SHOULD_LIMIT_SAVE)) { + int minX = propertyMap.getValue(SlimeProperties.SAVE_MIN_X); + int maxX = propertyMap.getValue(SlimeProperties.SAVE_MAX_X); + + int minZ = propertyMap.getValue(SlimeProperties.SAVE_MIN_Z); + int maxZ = propertyMap.getValue(SlimeProperties.SAVE_MAX_Z); + + int chunkX = chunk.locX; + int chunkZ = chunk.locZ; + + if (chunkX < minX || chunkX > maxX) { + return true; + } + + if (chunkZ < minZ || chunkZ > maxZ) { + return true; + } + } + + String pruningSetting = world.getPropertyMap().getValue(SlimeProperties.CHUNK_PRUNING); + if (pruningSetting.equals("aggressive")) { + ChunkEntitySlices slices = chunk.getChunkHolder().getEntityChunk(); + + return chunk.blockEntities.isEmpty() && (slices == null || slices.isEmpty()) && areSectionsEmpty(chunk); + } + + return false; + } + + private static boolean areSectionsEmpty(LevelChunk chunk) { + for (LevelChunkSection section : chunk.getSections()) { + if (!section.hasOnlyAir()) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java new file mode 100644 index 0000000000000000000000000000000000000000..c94ee5460d3859d373ae81e9d3623db071d6c38b --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java @@ -0,0 +1,203 @@ +package com.infernalsuite.aswm.level; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.LongArrayTag; +import com.google.common.collect.Lists; +import com.infernalsuite.aswm.Converter; +import com.infernalsuite.aswm.api.utils.NibbleArray; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeChunkSection; +import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import io.papermc.paper.world.ChunkEntitySlices; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class NMSSlimeChunk implements SlimeChunk { + private static final Logger LOGGER = LogUtils.getClassLogger(); + + private static final CompoundTag EMPTY_BLOCK_STATE_PALETTE; + private static final CompoundTag EMPTY_BIOME_PALETTE; + + // Optimized empty section serialization + static { + { + PalettedContainer empty = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); + Tag tag = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> { + throw new AssertionError(error); + }); + + EMPTY_BLOCK_STATE_PALETTE = (CompoundTag) Converter.convertTag("", tag); + } + { + Registry biomes = net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME); + PalettedContainer> empty = new PalettedContainer<>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); + Tag tag = ChunkSerializer.makeBiomeCodec(biomes).encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> { + throw new AssertionError(error); + }); + + EMPTY_BIOME_PALETTE = (CompoundTag) Converter.convertTag("", tag); + } + } + + private LevelChunk chunk; + + public NMSSlimeChunk(LevelChunk chunk) { + this.chunk = chunk; + } + + @Override + public int getX() { + return chunk.getPos().x; + } + + @Override + public int getZ() { + return chunk.getPos().z; + } + + @Override + public SlimeChunkSection[] getSections() { + SlimeChunkSection[] sections = new SlimeChunkSection[this.chunk.getSectionsCount()]; + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + + Registry biomeRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.BIOME); + + // Ignore deprecation, spigot only method + Codec>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); + + for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) { + LevelChunkSection section = chunk.getSections()[sectionId]; + // Sections CANNOT be null in 1.18 + + // Block Light Nibble Array + NibbleArray blockLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId))); + + // Sky light Nibble Array + NibbleArray skyLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId))); + + // Tile/Entity Data + + // Block Data + CompoundTag blockStateTag; + if (section.hasOnlyAir()) { + blockStateTag = EMPTY_BLOCK_STATE_PALETTE; + } else { + Tag data = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, System.err::println); // todo error handling + blockStateTag = (CompoundTag) Converter.convertTag("", data); + } + + + CompoundTag biomeTag; + PalettedContainer> biomes = (PalettedContainer>) section.getBiomes(); + if (biomes.data.palette().getSize() == 1 && biomes.data.palette().maybeHas((h) -> h.is(Biomes.PLAINS))) { + biomeTag = EMPTY_BIOME_PALETTE; + } else { + Tag biomeData = codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, System.err::println); // todo error handling + biomeTag = (CompoundTag) Converter.convertTag("", biomeData); + } + + sections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); + } + + return sections; + } + + @Override + public CompoundTag getHeightMaps() { + // HeightMap + CompoundMap heightMaps = new CompoundMap(); + + for (Map.Entry entry : chunk.heightmaps.entrySet()) { + if (!entry.getKey().keepAfterWorldgen()) { + continue; + } + + Heightmap.Types type = entry.getKey(); + Heightmap map = entry.getValue(); + + heightMaps.put(type.name(), new LongArrayTag(type.name(), map.getRawData())); + } + + return new CompoundTag("", heightMaps); + } + + @Override + public List getTileEntities() { + List tileEntities = new ArrayList<>(); + + for (BlockEntity entity : chunk.blockEntities.values()) { + net.minecraft.nbt.CompoundTag entityNbt = entity.saveWithFullMetadata(); + tileEntities.add(entityNbt); + } + + return Lists.transform(tileEntities, (compound) -> { + return (CompoundTag) Converter.convertTag("", compound); + }); + } + + @Override + public List getEntities() { + List entities = new ArrayList<>(); + + if(this.chunk == null || this.chunk.getChunkHolder() == null) { + return new ArrayList<>(); + } + + ChunkEntitySlices slices = this.chunk.getChunkHolder().getEntityChunk(); + if (slices == null) { + return new ArrayList<>(); + } + + // Work by + for (Entity entity : slices.entities) { + net.minecraft.nbt.CompoundTag entityNbt = new net.minecraft.nbt.CompoundTag(); + try { + if (entity.save(entityNbt)) { + entities.add(entityNbt); + } + } catch (Exception e) { + LOGGER.error("Could not save the entity = {}, exception = {}", entity, e); + } + } + + return Lists.transform(entities, (compound) -> { + return (CompoundTag) Converter.convertTag("", compound); + }); + } + + public LevelChunk getChunk() { + return chunk; + } + + public void setChunk(LevelChunk chunk) { + this.chunk = chunk; + } + +} diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java new file mode 100644 index 0000000000000000000000000000000000000000..9a27369c00345bbb94aa19f77687269dc94c0b0a --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java @@ -0,0 +1,91 @@ +package com.infernalsuite.aswm.level; + +import com.flowpowered.nbt.CompoundTag; +import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException; +import com.infernalsuite.aswm.api.loaders.SlimeLoader; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import net.minecraft.SharedConstants; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.world.level.chunk.LevelChunk; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class NMSSlimeWorld implements SlimeWorld { + + private final SlimeInMemoryWorld memoryWorld; + private final SlimeLevelInstance instance; + + public NMSSlimeWorld(SlimeInMemoryWorld memoryWorld) { + this.instance = memoryWorld.getInstance(); + this.memoryWorld = memoryWorld; + } + + @Override + public String getName() { + return this.instance.getMinecraftWorld().serverLevelData.getLevelName(); + } + + @Override + public SlimeLoader getLoader() { + return this.instance.slimeInstance.getSaveStrategy(); + } + + @Override + public SlimeChunk getChunk(int x, int z) { + LevelChunk chunk = this.instance.getChunkIfLoaded(x, z); + if (chunk == null) { + return null; + } + + return new NMSSlimeChunk(chunk); + } + + @Override + public Collection getChunkStorage() { + List chunks = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.instance); // Paper + return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull) + .map(NMSSlimeChunk::new) + .collect(Collectors.toList()); + } + + @Override + public CompoundTag getExtraData() { + return this.instance.slimeInstance.getExtraData(); + } + + @Override + public Collection getWorldMaps() { + return List.of(); + } + + @Override + public SlimePropertyMap getPropertyMap() { + return this.instance.slimeInstance.getPropertyMap(); + } + + @Override + public boolean isReadOnly() { + return this.getLoader() == null; + } + + @Override + public SlimeWorld clone(String worldName) { + return this.memoryWorld.clone(worldName); + } + + @Override + public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException { + return this.memoryWorld.clone(worldName, loader); + } + + @Override + public int getDataVersion() { + return SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..b20a037679182e3c4a8bf31f084078f6d7e4ff46 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java @@ -0,0 +1,83 @@ +package com.infernalsuite.aswm.level; + +import com.flowpowered.nbt.CompoundTag; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeChunkSection; + +import java.util.List; + +public class SafeNmsChunkWrapper implements SlimeChunk { + + private final NMSSlimeChunk wrapper; + private final SlimeChunk safety; + + public SafeNmsChunkWrapper(NMSSlimeChunk wrapper, SlimeChunk safety) { + this.wrapper = wrapper; + this.safety = safety; + } + + @Override + public int getX() { + return this.wrapper.getX(); + } + + @Override + public int getZ() { + return this.wrapper.getZ(); + } + + @Override + public SlimeChunkSection[] getSections() { + if (shouldDefaultBackToSlimeChunk()) { + return this.safety.getSections(); + } + + return this.wrapper.getSections(); + } + + @Override + public CompoundTag getHeightMaps() { + if (shouldDefaultBackToSlimeChunk()) { + return this.safety.getHeightMaps(); + } + + return this.wrapper.getHeightMaps(); + } + + @Override + public List getTileEntities() { + if (shouldDefaultBackToSlimeChunk()) { + return this.safety.getTileEntities(); + } + + return this.wrapper.getTileEntities(); + } + + @Override + public List getEntities() { + if (shouldDefaultBackToSlimeChunk()) { + return this.safety.getEntities(); + } + + return this.wrapper.getEntities(); + } + + /* +Slime chunks can still be requested but not actually loaded, this caused +some things to not properly save because they are not "loaded" into the chunk. +See ChunkMap#protoChunkToFullChunk +anything in the if statement will not be loaded and is stuck inside the runnable. +Inorder to possibly not corrupt the state, simply refer back to the slime saved object. +*/ + public boolean shouldDefaultBackToSlimeChunk() { + return this.safety != null && !this.wrapper.getChunk().loaded; + } + + public NMSSlimeChunk getWrapper() { + return wrapper; + } + + public SlimeChunk getSafety() { + return safety; + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java new file mode 100644 index 0000000000000000000000000000000000000000..8853088c5c6306511716bbffac9bf73c633b61bb --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java @@ -0,0 +1,8 @@ +package com.infernalsuite.aswm.level; + +import com.infernalsuite.aswm.api.world.SlimeWorld; + +public record SlimeBootstrap( + SlimeWorld initial +) { +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..a771d4975d47246d4d1597492daf0e19d6e25558 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java @@ -0,0 +1,172 @@ +package com.infernalsuite.aswm.level; + +import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.LongArrayTag; +import com.infernalsuite.aswm.Converter; +import com.infernalsuite.aswm.api.utils.NibbleArray; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeChunkSection; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.NbtOps; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.LevelChunkTicks; + +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; + +public class SlimeChunkConverter { + + static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, SlimeChunk chunk) { + int x = chunk.getX(); + int z = chunk.getZ(); + + ChunkPos pos = new ChunkPos(x, z); + + // Chunk sections + LevelChunkSection[] sections = new LevelChunkSection[instance.getSectionsCount()]; + + SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance); + SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance); + instance.getServer().scheduleOnMain(() -> { + instance.getLightEngine().retainData(pos, true); + }); + + Registry biomeRegistry = instance.registryAccess().registryOrThrow(Registries.BIOME); + // Ignore deprecated method + + Codec>> codec = PalettedContainer.codecRW(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS), null); + + for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) { + SlimeChunkSection slimeSection = chunk.getSections()[sectionId]; + + if (slimeSection != null) { + NibbleArray blockLight = slimeSection.getBlockLight(); + if (blockLight != null) { + blockNibbles[sectionId] = new SWMRNibbleArray(blockLight.getBacking()); + } + + NibbleArray skyLight = slimeSection.getSkyLight(); + if (skyLight != null) { + skyNibbles[sectionId] = new SWMRNibbleArray(skyLight.getBacking()); + } + + PalettedContainer blockPalette; + if (slimeSection.getBlockStatesTag() != null) { + DataResult> dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBlockStatesTag())).promotePartial((s) -> { + System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging + }); + blockPalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging + } else { + blockPalette = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); + } + + PalettedContainer> biomePalette; + + if (slimeSection.getBiomeTag() != null) { + DataResult>> dataresult = codec.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBiomeTag())).promotePartial((s) -> { + System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging + }); + biomePalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging + } else { + biomePalette = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); + } + + if (sectionId < sections.length) { + LevelChunkSection section = new LevelChunkSection(blockPalette, biomePalette); + sections[sectionId] = section; + } + } + } + + // Keep the chunk loaded at level 33 to avoid light glitches + // Such a high level will let the server not tick the chunk, + // but at the same time it won't be completely unloaded from memory + // getChunkProvider().addTicket(SWM_TICKET, pos, 33, Unit.INSTANCE); + + + LevelChunk.PostLoadProcessor loadEntities = (nmsChunk) -> { + + // TODO + // Load tile entities + List tileEntities = chunk.getTileEntities(); + + if (tileEntities != null) { + for (CompoundTag tag : tileEntities) { + Optional type = tag.getStringValue("id"); + + // Sometimes null tile entities are saved + if (type.isPresent()) { + BlockPos blockPosition = new BlockPos(tag.getIntValue("x").get(), tag.getIntValue("y").get(), tag.getIntValue("z").get()); + BlockState blockData = nmsChunk.getBlockState(blockPosition); + BlockEntity entity = BlockEntity.loadStatic(blockPosition, blockData, (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)); + + if (entity != null) { + nmsChunk.setBlockEntity(entity); + } + } + } + } + + List entities = chunk.getEntities(); + + if (entities != null) { + instance.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(entities.stream() + .map(flowTag -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(flowTag)).toList(), instance), nmsChunk.getPos()); + } + }; + + LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); + LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); + SlimeChunkLevel nmsChunk = new SlimeChunkLevel(instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, 0L, sections, loadEntities, null); + + // Height Maps + EnumSet heightMapTypes = nmsChunk.getStatus().heightmapsAfter(); + CompoundMap heightMaps = chunk.getHeightMaps().getValue(); + EnumSet unsetHeightMaps = EnumSet.noneOf(Heightmap.Types.class); + + // Light + nmsChunk.setBlockNibbles(blockNibbles); + nmsChunk.setSkyNibbles(skyNibbles); + + for (Heightmap.Types type : heightMapTypes) { + String name = type.getSerializedName(); + + if (heightMaps.containsKey(name)) { + LongArrayTag heightMap = (LongArrayTag) heightMaps.get(name); + nmsChunk.setHeightmap(type, heightMap.getValue()); + } else { + unsetHeightMaps.add(type); + } + } + + // Don't try to populate heightmaps if there are none. + // Does a crazy amount of block lookups + if (!unsetHeightMaps.isEmpty()) { + Heightmap.primeHeightmaps(nmsChunk, unsetHeightMaps); + } + + return nmsChunk; + } +} diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..b159fc8751e9840b311cc1eda01e496e2dbc5f2e --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java @@ -0,0 +1,27 @@ +package com.infernalsuite.aswm.level; + +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.LevelChunkTicks; +import org.jetbrains.annotations.Nullable; + +public class SlimeChunkLevel extends LevelChunk { + + private final SlimeInMemoryWorld inMemoryWorld; + + public SlimeChunkLevel(SlimeLevelInstance world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { + super(world, pos, upgradeData, blockTickScheduler, fluidTickScheduler, inhabitedTime, sectionArrayInitializer, entityLoader, blendingData); + this.inMemoryWorld = world.slimeInstance; + } + + @Override + public void unloadCallback() { + super.unloadCallback(); + this.inMemoryWorld.unload(this); + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java new file mode 100644 index 0000000000000000000000000000000000000000..f0509f07a743302dfedce9413b0d9c8fb3ff111d --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java @@ -0,0 +1,259 @@ +package com.infernalsuite.aswm.level; + +import com.flowpowered.nbt.CompoundTag; +import com.infernalsuite.aswm.ChunkPos; +import com.infernalsuite.aswm.Converter; +import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException; +import com.infernalsuite.aswm.api.loaders.SlimeLoader; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.SlimeWorldInstance; +import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import com.infernalsuite.aswm.serialization.slime.SlimeSerializer; +import com.infernalsuite.aswm.skeleton.SkeletonCloning; +import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; +import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.LevelChunkTicks; +import org.bukkit.World; + +import java.io.IOException; +import java.util.*; + +/* +The concept of this is a bit flawed, since ideally this should be a 1:1 representation of the MC world. +However, due to the complexity of the chunk system we essentially need to wrap around it. +This stores slime chunks, and when unloaded, will properly convert it to a normal slime chunk for storage. + */ +public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { + + private final SlimeLevelInstance instance; + private final SlimeWorld liveWorld; + + private final CompoundTag extra; + private final SlimePropertyMap propertyMap; + private final SlimeLoader loader; + + private final Map chunkStorage = new HashMap<>(); + private boolean readOnly; + // private final Map> entityStorage = new HashMap<>(); + + public SlimeInMemoryWorld(SlimeBootstrap bootstrap, SlimeLevelInstance instance) { + this.instance = instance; + this.extra = bootstrap.initial().getExtraData(); + this.propertyMap = bootstrap.initial().getPropertyMap(); + this.loader = bootstrap.initial().getLoader(); + this.readOnly = bootstrap.initial().isReadOnly(); + + for (SlimeChunk initial : bootstrap.initial().getChunkStorage()) { + ChunkPos pos = new ChunkPos(initial.getX(), initial.getZ()); + List tags = new ArrayList<>(initial.getEntities()); + + // this.entityStorage.put(pos, tags); + this.chunkStorage.put(pos, initial); + } + + this.liveWorld = new NMSSlimeWorld(this); + } + + @Override + public String getName() { + return this.instance.getMinecraftWorld().serverLevelData.getLevelName(); + } + + @Override + public SlimeLoader getLoader() { + return this.loader; + } + + public LevelChunk promote(int x, int z, SlimeChunk chunk) { + SlimeChunkLevel levelChunk; + if (chunk == null) { + net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(x, z); + LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); + LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); + + levelChunk = new SlimeChunkLevel(this.instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, + 0L, null, null, null); + + chunk = new NMSSlimeChunk(levelChunk); + + } else { + levelChunk = SlimeChunkConverter.deserializeSlimeChunk(this.instance, chunk); + chunk = new SafeNmsChunkWrapper(new NMSSlimeChunk(levelChunk), chunk); + } + this.chunkStorage.put(new ChunkPos(x, z), chunk); + + return levelChunk; + } + + // Authored by: Kenox + // Don't use the NMS live chunk in the chunk map + public void unload(LevelChunk providedChunk) { + final int x = providedChunk.locX; + final int z = providedChunk.locZ; + + SlimeChunk chunk = new NMSSlimeChunk(providedChunk); + + if (FastChunkPruner.canBePruned(this.liveWorld, providedChunk)) { + this.chunkStorage.remove(new ChunkPos(x, z)); + return; + } + + this.chunkStorage.put(new ChunkPos(x, z), + new SlimeChunkSkeleton(chunk.getX(), chunk.getZ(), chunk.getSections(), + chunk.getHeightMaps(), chunk.getTileEntities(), chunk.getEntities())); + } + + @Override + public SlimeChunk getChunk(int x, int z) { + return this.chunkStorage.get(new ChunkPos(x, z)); + } + + @Override + public Collection getChunkStorage() { + return this.chunkStorage.values(); + } + + @Override + public World getBukkitWorld() { + return this.instance.getWorld(); + } + + @Override + public SlimeWorld getSlimeWorldMirror() { + return this.liveWorld; + } + + @Override + public SlimePropertyMap getPropertyMap() { + return this.propertyMap; + } + + @Override + public boolean isReadOnly() { + return this.getSaveStrategy() == null || this.readOnly; + } + + @Override + public SlimeWorld clone(String worldName) { + try { + return clone(worldName, null); + } catch (WorldAlreadyExistsException | IOException ignored) { + return null; // Never going to happen + } + } + + @Override + public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException { + if (this.getName().equals(worldName)) { + throw new IllegalArgumentException("The clone world cannot have the same name as the original world!"); + } + + if (worldName == null) { + throw new IllegalArgumentException("The world name cannot be null!"); + } + if (loader != null) { + if (loader.worldExists(worldName)) { + throw new WorldAlreadyExistsException(worldName); + } + } + + SlimeWorld cloned = SkeletonCloning.fullClone(worldName, this, loader); + if (loader != null) { + loader.saveWorld(worldName, SlimeSerializer.serialize(cloned)); + } + + return cloned; + } + + @Override + public int getDataVersion() { + return this.liveWorld.getDataVersion(); + } + + @Override + public SlimeLoader getSaveStrategy() { + return this.loader; + } + + @Override + public CompoundTag getExtraData() { + return this.extra; + } + + @Override + public Collection getWorldMaps() { + return List.of(); + } + + // public Map> getEntityStorage() { + // return entityStorage; + // } + + public SlimeWorld getForSerialization() { + SlimeWorld world = SkeletonCloning.weakCopy(this); + + Map cloned = new HashMap<>(); + for (Map.Entry entry : this.chunkStorage.entrySet()) { + SlimeChunk clonedChunk = entry.getValue(); + // NMS "live" chunks need to be converted + { + LevelChunk chunk = null; + if (clonedChunk instanceof SafeNmsChunkWrapper safeNmsChunkWrapper) { + if (safeNmsChunkWrapper.shouldDefaultBackToSlimeChunk()) { + clonedChunk = safeNmsChunkWrapper.getSafety(); + } else { + chunk = safeNmsChunkWrapper.getWrapper().getChunk(); + } + } else if (clonedChunk instanceof NMSSlimeChunk nmsSlimeChunk) { + chunk = nmsSlimeChunk.getChunk(); + } + + if (chunk != null) { + if (FastChunkPruner.canBePruned(world, chunk)) { + continue; + } + + clonedChunk = new SlimeChunkSkeleton( + clonedChunk.getX(), + clonedChunk.getZ(), + clonedChunk.getSections(), + clonedChunk.getHeightMaps(), + clonedChunk.getTileEntities(), + clonedChunk.getEntities() + ); + } + } + + cloned.put(entry.getKey(), clonedChunk); + } + + // Serialize Bukkit Values (PDC) + + var nmsTag = new net.minecraft.nbt.CompoundTag(); + + instance.getWorld().storeBukkitValues(nmsTag); + + // Bukkit stores the relevant tag as a tag with the key "BukkitValues" in the tag we supply to it + var flowTag = Converter.convertTag("BukkitValues", nmsTag.getCompound("BukkitValues")); + + world.getExtraData().getValue().put(flowTag); + + return new SkeletonSlimeWorld(world.getName(), + world.getLoader(), + world.isReadOnly(), + cloned, + world.getExtraData(), + world.getPropertyMap(), + world.getDataVersion() + ); + } + + public SlimeLevelInstance getInstance() { + return instance; + } +} diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..4f48b7a1a41aabc78cc9276fbf9f372cb117003f --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java @@ -0,0 +1,39 @@ +package com.infernalsuite.aswm.level; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class SlimeLevelGenerator extends FlatLevelSource { + + public SlimeLevelGenerator(Holder biome) { + super(new FlatLevelGeneratorSettings(Optional.empty(), biome, List.of()), getSource(biome)); + } + + private static BiomeSource getSource(Holder biome) { + return new BiomeSource() { + @Override + protected Codec codec() { + return null; + } + + @Override + protected Stream> collectPossibleBiomes() { + return Stream.of(biome); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler noise) { + return biome; + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java new file mode 100644 index 0000000000000000000000000000000000000000..10fde8e966dd89b8371c764f0c6fc08a0e28ac44 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java @@ -0,0 +1,195 @@ +package com.infernalsuite.aswm.level; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.infernalsuite.aswm.Converter; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.SlimeWorldInstance; +import com.infernalsuite.aswm.api.world.properties.SlimeProperties; +import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import com.infernalsuite.aswm.serialization.slime.SlimeSerializer; +import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask; +import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler; +import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.ProgressListener; +import net.minecraft.util.Unit; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.world.Difficulty; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.validation.DirectoryValidator; +import org.apache.commons.io.FileUtils; +import org.bukkit.Bukkit; +import org.bukkit.event.world.WorldSaveEvent; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class SlimeLevelInstance extends ServerLevel { + + + public static LevelStorageSource CUSTOM_LEVEL_STORAGE; + + static { + try { + Path path = Files.createTempDirectory("swm-" + UUID.randomUUID().toString().substring(0, 5)).toAbsolutePath(); + DirectoryValidator directoryvalidator = LevelStorageSource.parseValidator(path.resolve("allowed_symlinks.txt")); + CUSTOM_LEVEL_STORAGE = new LevelStorageSource(path, path, directoryvalidator, DataFixers.getDataFixer()); + + FileUtils.forceDeleteOnExit(path.toFile()); + + } catch (IOException ex) { + throw new IllegalStateException("Couldn't create dummy file directory.", ex); + } + } + + private static final ExecutorService WORLD_SAVER_SERVICE = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder() + .setNameFormat("SWM Pool Thread #%1$d").build()); + private static final TicketType SWM_TICKET = TicketType.create("swm-chunk", (a, b) -> 0); + + private final Object saveLock = new Object(); + + private boolean ready = false; + + public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primaryLevelData, + ResourceKey worldKey, + ResourceKey dimensionKey, LevelStem worldDimension, + org.bukkit.World.Environment environment) throws IOException { + + super(slimeBootstrap, MinecraftServer.getServer(), MinecraftServer.getServer().executor, + CUSTOM_LEVEL_STORAGE.createAccess(slimeBootstrap.initial().getName() + UUID.randomUUID(), dimensionKey), + primaryLevelData, worldKey, worldDimension, + MinecraftServer.getServer().progressListenerFactory.create(11), false, null, 0, + Collections.emptyList(), true, environment, null, null); + this.slimeInstance = new SlimeInMemoryWorld(slimeBootstrap, this); + + + SlimePropertyMap propertyMap = slimeBootstrap.initial().getPropertyMap(); + + this.serverLevelData.setDifficulty(Difficulty.valueOf(propertyMap.getValue(SlimeProperties.DIFFICULTY).toUpperCase())); + this.serverLevelData.setSpawn(new BlockPos(propertyMap.getValue(SlimeProperties.SPAWN_X), propertyMap.getValue(SlimeProperties.SPAWN_Y), propertyMap.getValue(SlimeProperties.SPAWN_Z)), 0); + super.setSpawnSettings(propertyMap.getValue(SlimeProperties.ALLOW_MONSTERS), propertyMap.getValue(SlimeProperties.ALLOW_ANIMALS)); + + this.pvpMode = propertyMap.getValue(SlimeProperties.PVP); + + this.keepSpawnInMemory = false; + } + + @Override + public ChunkGenerator getGenerator(SlimeBootstrap slimeBootstrap) { + String biomeStr = slimeBootstrap.initial().getPropertyMap().getValue(SlimeProperties.DEFAULT_BIOME); + ResourceKey biomeKey = ResourceKey.create(Registries.BIOME, new ResourceLocation(biomeStr)); + Holder defaultBiome = MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolder(biomeKey).orElseThrow(); + return new SlimeLevelGenerator(defaultBiome); + } + + @Override + public void save(@Nullable ProgressListener progressUpdate, boolean forceSave, boolean savingDisabled, boolean close) { + try { + if (!this.slimeInstance.isReadOnly() && !savingDisabled) { + Bukkit.getPluginManager().callEvent(new WorldSaveEvent(getWorld())); + + //this.getChunkSource().save(forceSave); + this.serverLevelData.setWorldBorder(this.getWorldBorder().createSettings()); + this.serverLevelData.setCustomBossEvents(MinecraftServer.getServer().getCustomBossEvents().save()); + + // Update level data + net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag(); + net.minecraft.nbt.CompoundTag nbtTagCompound = this.serverLevelData.createTag(MinecraftServer.getServer().registryAccess(), compound); + + if (MinecraftServer.getServer().isStopped()) { // Make sure the world gets saved before stopping the server by running it from the main thread + save().get(); // Async wait for it to finish + this.slimeInstance.getLoader().unlockWorld(this.slimeInstance.getName()); // Unlock + } else { + this.save(); + //WORLD_SAVER_SERVICE.execute(this::save); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void saveIncrementally(boolean doFull) { + if (doFull) { + this.save(null, false, false); + } + } + + private Future save() { + synchronized (saveLock) { // Don't want to save the SlimeWorld from multiple threads simultaneously + SlimeWorldInstance slimeWorld = this.slimeInstance; + Bukkit.getLogger().log(Level.INFO, "Saving world " + this.slimeInstance.getName() + "..."); + long start = System.currentTimeMillis(); + + Bukkit.getLogger().log(Level.INFO, "CONVERTING NMS -> SKELETON"); + SlimeWorld world = this.slimeInstance.getForSerialization(); + Bukkit.getLogger().log(Level.INFO, "CONVERTED TO SKELETON, PUSHING OFF-THREAD"); + return WORLD_SAVER_SERVICE.submit(() -> { + try { + byte[] serializedWorld = SlimeSerializer.serialize(world); + long saveStart = System.currentTimeMillis(); + slimeWorld.getSaveStrategy().saveWorld(slimeWorld.getName(), serializedWorld); + Bukkit.getLogger().log(Level.INFO, "World " + slimeWorld.getName() + " serialized in " + (saveStart - start) + "ms and saved in " + (System.currentTimeMillis() - saveStart) + "ms."); + } catch (IOException | IllegalStateException ex) { + ex.printStackTrace(); + } + }); + + } + } + + public SlimeWorldInstance getSlimeInstance() { + return this.slimeInstance; + } + + public ChunkDataLoadTask getLoadTask(ChunkLoadTask task, ChunkTaskScheduler scheduler, ServerLevel world, int chunkX, int chunkZ, PrioritisedExecutor.Priority priority, Consumer> onRun) { + return new ChunkDataLoadTask(task, scheduler, world, chunkX, chunkZ, priority, onRun); + } + + public void loadEntities(int chunkX, int chunkZ) { + SlimeChunk slimeChunk = this.slimeInstance.getChunk(chunkX, chunkZ); + if (slimeChunk != null) { + this.getEntityLookup().addLegacyChunkEntities(new ArrayList<>( + EntityType.loadEntitiesRecursive(slimeChunk.getEntities() + .stream() + .map((tag) -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)) + .collect(Collectors.toList()), this) + .toList() + ), new ChunkPos(chunkX, chunkZ)); + } + } + + // @Override + // public void unload(LevelChunk chunk) { + // this.slimeInstance.unload(chunk); + // super.unload(chunk); + // } +} diff --git a/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..3500005bb09dc484bc333f1e0799613d097a37d3 --- /dev/null +++ b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java @@ -0,0 +1,9 @@ +package com.infernalsuite.aswm.util; + +public class NmsUtil { + + public static long asLong(int chunkX, int chunkZ) { + return (((long) chunkZ) * Integer.MAX_VALUE + ((long) chunkX)); + //return (long)chunkX & 4294967295L | ((long)chunkZ & 4294967295L) << 32; + } +} \ No newline at end of file diff --git a/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java index d72041aa814ff179e6e29a45dcd359a91d426d47..9c16cd0728d2252cf4dadb45779e2ad0e8ca4c0e 100644 --- a/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java +++ b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java @@ -18,7 +18,7 @@ import org.slf4j.Logger; import java.util.Optional; -public final class PoiChunk { +public class PoiChunk { private static final Logger LOGGER = LogUtils.getClassLogger(); diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java index 6bc7c6f16a1649fc9e24e7cf90fca401e5bd4875..8bad563d6ff3ae3a41e7ff3170e4bce3bc74136d 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java @@ -166,7 +166,8 @@ public final class ChunkHolderManager { return this.chunkHolders.size(); } - public void close(final boolean save, final boolean halt) { + public void close(boolean save, final boolean halt) { // ASWM + if (this.world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance) save = false; // ASWM TickThread.ensureTickThread("Closing world off-main"); if (halt) { LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); @@ -1317,7 +1318,7 @@ public final class ChunkHolderManager { public boolean processTicketUpdates() { co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager - return this.processTicketUpdates(true, true, null); + return this.processTicketUpdates(true, true, null); } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java index e7fb084ddb88ab62f1d493a999cc82b9258d275e..943cdd570c9e5c87ee9f3984404e08b4cdffeca0 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java @@ -6,6 +6,7 @@ import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; import ca.spottedleaf.dataconverter.minecraft.MCDataConverter; import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; +import com.infernalsuite.aswm.level.CommonLoadTask; import com.mojang.logging.LogUtils; import io.papermc.paper.chunk.system.io.RegionFileIOThread; import io.papermc.paper.chunk.system.poi.PoiChunk; @@ -32,8 +33,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask { private static final Logger LOGGER = LogUtils.getClassLogger(); - private final NewChunkHolder chunkHolder; - private final ChunkDataLoadTask loadTask; + public final NewChunkHolder chunkHolder; // ASWM + private final CommonLoadTask loadTask; private volatile boolean cancelled; private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; @@ -45,11 +46,20 @@ public final class ChunkLoadTask extends ChunkProgressionTask { final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { super(scheduler, world, chunkX, chunkZ); this.chunkHolder = chunkHolder; - this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); - this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { - ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement - ChunkLoadTask.this.tryCompleteLoad(); - }); + // ASWM start + if (world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance levelInstance) { + + this.loadTask = levelInstance.getLoadTask(this, scheduler, world, chunkX, chunkZ, priority, result -> { + ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); + }); + } else { + ChunkDataLoadTask task = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); + task.addCallback((final GenericDataLoadTask.TaskResult result) -> { + ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); + }); + this.loadTask = task; + } + // ASWM end } private void tryCompleteLoad() { @@ -274,7 +284,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { } } - public static final class ChunkDataLoadTask extends CallbackDataLoadTask { + public static final class ChunkDataLoadTask extends CallbackDataLoadTask implements CommonLoadTask { // ASWM protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java index 7e8dc9e8f381abfdcce2746edc93122d623622d1..12aadf4c981cdb1a6405de99016f4b40e83a04b8 100644 --- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java @@ -36,7 +36,7 @@ public final class ChunkEntitySlices { protected final EntityCollectionBySection allEntities; protected final EntityCollectionBySection hardCollidingEntities; protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; - protected final EntityList entities = new EntityList(); + public final EntityList entities = new EntityList(); // ASWM public FullChunkStatus status; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 34f19ac897a30c0c4e3ab406013fcca1c8b7db93..d6f329f4c9534d45533774ad2fadec709365297a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -273,7 +273,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); // Paper end - public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { + // ASWM START + public final com.infernalsuite.aswm.level.SlimeBootstrap bootstrap; + public ServerChunkCache(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { // ASWM + this.bootstrap = bootstrap; + // ASWM end this.level = world; this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world); this.mainThread = Thread.currentThread(); @@ -294,7 +298,7 @@ public class ServerChunkCache extends ChunkSource { // Paper end com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); + chunkproviderserver_b.managedBlock(completablefuture::isDone); io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system this.level.timings.syncChunkLoad.stopTiming(); // Paper } // Paper @@ -446,7 +450,7 @@ public class ServerChunkCache extends ChunkSource { public void save(boolean flush) { this.runDistanceManagerUpdates(); try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings - this.chunkMap.saveAllChunks(flush); + this.chunkMap.saveAllChunks(flush); } // Paper - Timings } @@ -516,10 +520,43 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.push("pollingChunks"); gameprofilerfiller.push("filteringLoadedChunks"); - // Paper - optimise chunk tick iteration if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper - // Paper - optimise chunk tick iteration + // Paper start - optimise chunk tick iteration + ChunkMap playerChunkMap = this.chunkMap; + for (ServerPlayer player : this.level.players) { + if (!player.affectsSpawning || player.isSpectator()) { + playerChunkMap.playerMobSpawnMap.remove(player); + player.playerNaturallySpawnedEvent = null; + player.lastEntitySpawnRadiusSquared = -1.0; + continue; + } + + int viewDistance = io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player); + + // copied and modified from isOutisdeRange + int chunkRange = (int)level.spigotConfig.mobSpawnRange; + chunkRange = (chunkRange > viewDistance) ? viewDistance : chunkRange; + chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; + + com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); + event.callEvent(); + if (event.isCancelled() || event.getSpawnRadius() < 0) { + playerChunkMap.playerMobSpawnMap.remove(player); + player.playerNaturallySpawnedEvent = null; + player.lastEntitySpawnRadiusSquared = -1.0; + continue; + } + + int range = Math.min(event.getSpawnRadius(), DistanceManager.MOB_SPAWN_RANGE); // limit to max spawn range + int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getX()); + int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getZ()); + + playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); + player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning + player.playerNaturallySpawnedEvent = event; + } + // Paper end - optimise chunk tick iteration if (this.level.getServer().tickRateManager().runsNormally()) { gameprofilerfiller.popPush("naturalSpawnCount"); @@ -555,7 +592,6 @@ public class ServerChunkCache extends ChunkSource { boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit // Paper start - optimise chunk tick iteration - ChunkMap playerChunkMap = this.chunkMap; for (ServerPlayer player : this.level.players) { if (!player.affectsSpawning || player.isSpectator()) { playerChunkMap.playerMobSpawnMap.remove(player); @@ -591,7 +627,6 @@ public class ServerChunkCache extends ChunkSource { // Paper end - optimise chunk tick iteration int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - // Paper - optimise chunk tick iteration int chunksTicked = 0; // Paper // Paper start - optimise chunk tick iteration @@ -609,48 +644,48 @@ public class ServerChunkCache extends ChunkSource { chunkIterator = shuffled.iterator(); } try { - // Paper end - optimise chunk tick iteration - while (chunkIterator.hasNext()) { - LevelChunk chunk1 = chunkIterator.next(); // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); + while (chunkIterator.hasNext()) { + LevelChunk chunk1 = chunkIterator.next(); // Paper - optimise chunk tick iteration + ChunkPos chunkcoordintpair = chunk1.getPos(); - // Paper start - optimise chunk tick iteration - com.destroystokyo.paper.util.maplist.ReferenceList playersNearby - = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE); - if (playersNearby == null) { - continue; - } - Object[] rawData = playersNearby.getRawData(); - boolean spawn = false; - boolean tick = false; - for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) { - ServerPlayer player = (ServerPlayer)rawData[itr]; - if (player.isSpectator()) { + // Paper start - optimise chunk tick iteration + com.destroystokyo.paper.util.maplist.ReferenceList playersNearby + = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE); + if (playersNearby == null) { continue; } - double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player); - spawn |= player.lastEntitySpawnRadiusSquared >= distance; - tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance; - if (spawn & tick) { - break; - } - } - if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { - // Paper end - optimise chunk tick iteration - chunk1.incrementInhabitedTime(j); - if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + Object[] rawData = playersNearby.getRawData(); + boolean spawn = false; + boolean tick = false; + for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) { + ServerPlayer player = (ServerPlayer)rawData[itr]; + if (player.isSpectator()) { + continue; + } + + double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player); + spawn |= player.lastEntitySpawnRadiusSquared >= distance; + tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance; + if (spawn & tick) { + break; + } } + if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { + // Paper end - optimise chunk tick iteration + chunk1.incrementInhabitedTime(j); + if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } - if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration - this.level.tickChunk(chunk1, l); - if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration + this.level.tickChunk(chunk1, l); + if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + } } } - } - // Paper start - optimise chunk tick iteration + // Paper start - optimise chunk tick iteration } finally { if (chunkIterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { safeIterator.finishedIterating(); @@ -662,14 +697,14 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.popPush("customSpawners"); if (flag) { try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings - this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); + this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); } // Paper - timings } } gameprofilerfiller.popPush("broadcast"); // Paper - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing + this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Paper start - optimise chunk tick iteration if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); @@ -683,7 +718,7 @@ public class ServerChunkCache extends ChunkSource { } } // Paper end - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing + this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing // Paper - optimise chunk tick iteration gameprofilerfiller.pop(); gameprofilerfiller.pop(); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 676087c3addd712939c865b39ddb5d9f0bc7ce25..ca309311928654ca7e971e98114d7c16d8341a55 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -319,7 +319,7 @@ public class ServerLevel extends Level implements WorldGenLevel { java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { if (chunk != null) { synchronized (ret) { // Folia - region threading - make callback thread-safe TODO rebase - ret.add(chunk); + ret.add(chunk); } // Folia - region threading - make callback thread-safe TODO rebase chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); } @@ -694,6 +694,14 @@ public class ServerLevel extends Level implements WorldGenLevel { // Add env and gen to constructor, IWorldDataServer -> WorldDataServer public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // ASWM START + this(null, minecraftserver, executor, convertable_conversionsession, iworlddataserver, resourcekey, worlddimension, worldloadlistener, flag, randomsequences, i, list, flag1, env, gen, biomeProvider); + } + + public com.infernalsuite.aswm.level.SlimeInMemoryWorld slimeInstance; + + public ServerLevel(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, @Nullable RandomSequences randomsequences, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // ASWM END // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error // Holder holder = worlddimension.type(); // CraftBukkit - decompile error @@ -732,6 +740,12 @@ public class ServerLevel extends Level implements WorldGenLevel { chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen); } // CraftBukkit end + // ASWM START + ChunkGenerator result = this.getGenerator(bootstrap); + if (result != null) { + chunkgenerator = result; + } + // ASWM END boolean flag2 = minecraftserver.forceSynchronousWrites(); DataFixer datafixer = minecraftserver.getFixerUpper(); this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); @@ -743,7 +757,7 @@ public class ServerLevel extends Level implements WorldGenLevel { //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system - this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system + this.chunkSource = new ServerChunkCache(bootstrap, this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system // ASWM return minecraftserver.overworld().getDataStorage(); }); this.chunkSource.getGeneratorState().ensureStructuresGenerated(); @@ -791,6 +805,12 @@ public class ServerLevel extends Level implements WorldGenLevel { this.dragonFight = enderDragonFight; } + // ASWM START + public ChunkGenerator getGenerator(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap) { + return null; + } + // ASWM END + public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) { this.serverLevelData.setClearWeatherTime(clearDuration); this.serverLevelData.setRainTime(rainDuration); @@ -1028,14 +1048,14 @@ public class ServerLevel extends Level implements WorldGenLevel { gameprofilerfiller.popPush("iceandsnow"); if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - for (int l = 0; l < randomTickSpeed; ++l) { - if (this.random.nextInt(48) == 0) { - // Paper start - this.getRandomBlockPosition(j, 0, k, 15, blockposition); - this.tickPrecipitation(blockposition, chunk); - // Paper end + for (int l = 0; l < randomTickSpeed; ++l) { + if (this.random.nextInt(48) == 0) { + // Paper start + this.getRandomBlockPosition(j, 0, k, 15, blockposition); + this.tickPrecipitation(blockposition, chunk); + // Paper end + } } - } } // Paper gameprofilerfiller.popPush("tickBlocks"); @@ -1079,7 +1099,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public void tickPrecipitation(BlockPos pos) { - // Paper start - optimise chunk ticking + // Paper start - optimise chunk ticking tickPrecipitation(pos.mutable(), this.getChunkAt(pos)); } public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) { @@ -1386,9 +1406,9 @@ public class ServerLevel extends Level implements WorldGenLevel { currentlyTickingEntity.lazySet(entity); } // Paper end - log detailed entity tick information - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below entity.tickCount++; timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings @@ -1396,36 +1416,36 @@ public class ServerLevel extends Level implements WorldGenLevel { } finally { timer.stopTiming(); } // Paper return; }*/ // Paper - comment out EAR 2 - // Spigot end - // Paper start- timings - final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); - timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper - try { - // Paper end - timings - entity.setOldPosAndRot(); - ProfilerFiller gameprofilerfiller = this.getProfiler(); + // Spigot end + // Paper start- timings + final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); + timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper + try { + // Paper end - timings + entity.setOldPosAndRot(); + ProfilerFiller gameprofilerfiller = this.getProfiler(); - ++entity.tickCount; - this.getProfiler().push(() -> { - return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickNonPassenger"); - if (isActive) { // Paper - EAR 2 - TimingHistory.activatedEntityTicks++; - entity.tick(); - entity.postTick(); // CraftBukkit - } else { entity.inactiveTick(); } // Paper - EAR 2 - this.getProfiler().pop(); - } finally { timer.stopTiming(); } // Paper - timings - Iterator iterator = entity.getPassengers().iterator(); + ++entity.tickCount; + this.getProfiler().push(() -> { + return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickNonPassenger"); + if (isActive) { // Paper - EAR 2 + TimingHistory.activatedEntityTicks++; + entity.tick(); + entity.postTick(); // CraftBukkit + } else { entity.inactiveTick(); } // Paper - EAR 2 + this.getProfiler().pop(); + } finally { timer.stopTiming(); } // Paper - timings + Iterator iterator = entity.getPassengers().iterator(); - while (iterator.hasNext()) { - Entity entity1 = (Entity) iterator.next(); + while (iterator.hasNext()) { + Entity entity1 = (Entity) iterator.next(); - this.tickPassenger(entity, entity1); - } - // } finally { timer.stopTiming(); } // Paper - timings - move up - // Paper start - log detailed entity tick information + this.tickPassenger(entity, entity1); + } + // } finally { timer.stopTiming(); } // Paper - timings - move up + // Paper start - log detailed entity tick information } finally { if (currentlyTickingEntity.get() == entity) { currentlyTickingEntity.lazySet(null); @@ -1441,36 +1461,36 @@ public class ServerLevel extends Level implements WorldGenLevel { final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper try { - // Paper end - passenger.setOldPosAndRot(); - ++passenger.tickCount; - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push(() -> { - return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickPassenger"); - // Paper start - EAR 2 - if (isActive) { - passenger.rideTick(); - passenger.postTick(); // CraftBukkit - } else { - passenger.setDeltaMovement(Vec3.ZERO); - passenger.inactiveTick(); - // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary - vehicle.positionRider(passenger); - } - // Paper end - EAR 2 - gameprofilerfiller.pop(); - Iterator iterator = passenger.getPassengers().iterator(); + // Paper end + passenger.setOldPosAndRot(); + ++passenger.tickCount; + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push(() -> { + return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickPassenger"); + // Paper start - EAR 2 + if (isActive) { + passenger.rideTick(); + passenger.postTick(); // CraftBukkit + } else { + passenger.setDeltaMovement(Vec3.ZERO); + passenger.inactiveTick(); + // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary + vehicle.positionRider(passenger); + } + // Paper end - EAR 2 + gameprofilerfiller.pop(); + Iterator iterator = passenger.getPassengers().iterator(); - while (iterator.hasNext()) { - Entity entity2 = (Entity) iterator.next(); + while (iterator.hasNext()) { + Entity entity2 = (Entity) iterator.next(); - this.tickPassenger(passenger, entity2); - } + this.tickPassenger(passenger, entity2); + } - } finally { timer.stopTiming(); }// Paper - EAR2 timings + } finally { timer.stopTiming(); }// Paper - EAR2 timings } } else { passenger.stopRiding(); @@ -1524,18 +1544,18 @@ public class ServerLevel extends Level implements WorldGenLevel { if (!savingDisabled) { org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper - if (progressListener != null) { - progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); - } + if (progressListener != null) { + progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); + } - this.saveLevelData(); - if (progressListener != null) { - progressListener.progressStage(Component.translatable("menu.savingChunks")); - } + this.saveLevelData(); + if (progressListener != null) { + progressListener.progressStage(Component.translatable("menu.savingChunks")); + } timings.worldSaveChunks.startTiming(); // Paper - if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system - if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system + if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system + if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system timings.worldSaveChunks.stopTiming(); // Paper }// Paper // Paper - rewrite chunk system - entity saving moved into ChunkHolder @@ -1863,47 +1883,47 @@ public class ServerLevel extends Level implements WorldGenLevel { this.getChunkSource().blockChanged(pos); if(this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates - VoxelShape voxelshape = oldState.getCollisionShape(this, pos); - VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + VoxelShape voxelshape = oldState.getCollisionShape(this, pos); + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); - if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { - List list = new ObjectArrayList(); - Iterator iterator = this.navigatingMobs.iterator(); + if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { + List list = new ObjectArrayList(); + Iterator iterator = this.navigatingMobs.iterator(); - while (iterator.hasNext()) { - // CraftBukkit start - fix SPIGOT-6362 - Mob entityinsentient; - try { - entityinsentient = (Mob) iterator.next(); - } catch (java.util.ConcurrentModificationException ex) { - // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register - // In this case we just run the update again across all the iterators as the chunk will then be loaded - // As this is a relative edge case it is much faster than copying navigators (on either read or write) - this.sendBlockUpdated(pos, oldState, newState, flags); - return; - } - // CraftBukkit end - PathNavigation navigationabstract = entityinsentient.getNavigation(); + while (iterator.hasNext()) { + // CraftBukkit start - fix SPIGOT-6362 + Mob entityinsentient; + try { + entityinsentient = (Mob) iterator.next(); + } catch (java.util.ConcurrentModificationException ex) { + // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register + // In this case we just run the update again across all the iterators as the chunk will then be loaded + // As this is a relative edge case it is much faster than copying navigators (on either read or write) + this.sendBlockUpdated(pos, oldState, newState, flags); + return; + } + // CraftBukkit end + PathNavigation navigationabstract = entityinsentient.getNavigation(); - if (navigationabstract.shouldRecomputePath(pos)) { - list.add(navigationabstract); + if (navigationabstract.shouldRecomputePath(pos)) { + list.add(navigationabstract); + } } - } - try { - this.isUpdatingNavigations = true; - iterator = list.iterator(); + try { + this.isUpdatingNavigations = true; + iterator = list.iterator(); - while (iterator.hasNext()) { - PathNavigation navigationabstract1 = (PathNavigation) iterator.next(); + while (iterator.hasNext()) { + PathNavigation navigationabstract1 = (PathNavigation) iterator.next(); - navigationabstract1.recomputePath(); + navigationabstract1.recomputePath(); + } + } finally { + this.isUpdatingNavigations = false; } - } finally { - this.isUpdatingNavigations = false; - } - } + } } // Paper } diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java index 16fbc633de3a1d9e5e8c65ae107397a6f0e50811..d04c0147a87d4b31e6b5b090abeec316d235f1bd 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -31,7 +31,8 @@ public class PalettedContainer implements PaletteResize, PalettedContainer }; public final IdMap registry; private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values - private volatile PalettedContainer.Data data; + + public volatile PalettedContainer.Data data; // ASWM private final PalettedContainer.Strategy strategy; // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused @@ -399,7 +400,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer void accept(T object, int count); } - static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { + public static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { // ASWM public void copyFrom(Palette palette, BitStorage storage) { for(int i = 0; i < storage.getSize(); ++i) { T object = palette.valueFor(storage.get(i)); diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java index 1379084a80ce25644f13736b4a5ee5fabbd9ec1f..464e1c7970af5aa06ef563b823d7fd8b2776f8f5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java @@ -1,6 +1,9 @@ package net.minecraft.world.level.chunk.storage; import com.google.common.collect.Maps; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.level.NMSSlimeWorld; +import com.infernalsuite.aswm.level.SlimeLevelInstance; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; @@ -134,6 +137,7 @@ public class ChunkSerializer { public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { // Paper start - add variant for async calls InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true); + return holder.protoChunk; } // Paper start @@ -229,36 +233,40 @@ public class ChunkSerializer { // Paper start - rewrite the light engine if (flag) { try { - int y = sectionData.getByte("Y"); - // Paper end - rewrite the light engine - if (flag3) { - // Paper start - rewrite the light engine - // this is where our diff is - blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety - } else { - blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); + int y = sectionData.getByte("Y"); // Paper end - rewrite the light engine - } + if (flag3) { + // Paper start - rewrite the light engine + // this is where our diff is + blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety + } else { + blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); + // Paper end - rewrite the light engine + } - if (flag4) { - // Paper start - rewrite the light engine - // we store under the same key so mod programs editing nbt - // can still read the data, hopefully. - // however, for compatibility we store chunks as unlit so vanilla - // is forced to re-light them if it encounters our data. It's too much of a burden - // to try and maintain compatibility with a broken and inferior skylight management system. - skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety - } else if (flag1) { - skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); - // Paper end - rewrite the light engine - } + if (flag4) { + // Paper start - rewrite the light engine + // we store under the same key so mod programs editing nbt + // can still read the data, hopefully. + // however, for compatibility we store chunks as unlit so vanilla + // is forced to re-light them if it encounters our data. It's too much of a burden + // to try and maintain compatibility with a broken and inferior skylight management system. + skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety + } else if (flag1) { + skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); + // Paper end - rewrite the light engine + } - // Paper start - rewrite the light engine + // Paper start - rewrite the light engine } catch (Exception ex) { LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); flag = false; } // Paper end - rewrite light engine + + if(world instanceof SlimeLevelInstance) { + poiStorage.checkConsistencyWithBlocks(SectionPos.of(chunkPos.getWorldPosition()), achunksection[j]); + } } } @@ -441,7 +449,7 @@ public class ChunkSerializer { ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); } - private static Codec>> makeBiomeCodec(Registry biomeRegistry) { + public static Codec>> makeBiomeCodec(Registry biomeRegistry) { // ASWM return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); } @@ -620,7 +628,7 @@ public class ChunkSerializer { nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList); nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList); } else { - ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization()); + ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization()); } // Paper end nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing())); diff --git a/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge b/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge new file mode 100644 index 0000000000000000000000000000000000000000..d07947f0b42fe491617151e2aa7b0f02ff3ce610 --- /dev/null +++ b/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge @@ -0,0 +1 @@ +com.infernalsuite.aswm.SlimeNMSBridgeImpl \ No newline at end of file