Add system for dimensions to be marked for deletion (#6515)

This commit is contained in:
Take Weiland 2020-06-15 18:37:08 +02:00 committed by GitHub
parent 9114bec81d
commit 6345f2670c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 290 additions and 6 deletions

View file

@ -19,6 +19,7 @@
package net.minecraftforge.common;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@ -27,6 +28,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
@ -44,7 +47,9 @@ import com.google.common.collect.Multiset;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.StringNBT;
import net.minecraft.network.PacketBuffer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerList;
@ -58,6 +63,8 @@ import net.minecraft.world.chunk.listener.IChunkStatusListener;
import net.minecraft.world.server.ServerMultiWorld;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.storage.SaveHandler;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.event.world.RegisterDimensionsEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.StartupQuery;
@ -65,6 +72,7 @@ import net.minecraftforge.fml.server.ServerModLoader;
import net.minecraftforge.registries.ClearableRegistry;
import net.minecraftforge.registries.ForgeRegistries;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -85,6 +93,7 @@ public class DimensionManager
private static final ConcurrentMap<World, World> weakWorldMap = new MapMaker().weakKeys().weakValues().makeMap();
private static final Multiset<Integer> leakedWorlds = HashMultiset.create();
private static final Map<ResourceLocation, SavedEntry> savedEntries = new HashMap<>();
private static final List<String> foldersScheduledForDeletion = new ArrayList<>();
private static volatile Set<World> playerWorlds = new HashSet<>();
/**
@ -100,7 +109,8 @@ public class DimensionManager
* @param magnifier The biome generation processor
* @return the DimensionType for the dimension.
*/
public static DimensionType registerOrGetDimension(ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight) {
public static DimensionType registerOrGetDimension(ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight)
{
return REGISTRY.getValue(name).orElseGet(()->registerDimension(name, type, data, hasSkyLight));
}
/**
@ -199,14 +209,49 @@ public class DimensionManager
return ret;
}
/**
* Marks the given dimension for deletion upon next reload of the save.
* The corresponding data directory will be deleted as well.
*
* Note that the dimension need not be currently unloaded. The dimension will stay functioning as normal
* until the save is reloaded.
*
* Vanilla dimensions are not supported and will throw an exception.
*
* @param dim the dimension to delete
*/
public static void markForDeletion(DimensionType dim)
{
if (dim.isVanilla())
{
throw new IllegalArgumentException("Cannot delete vanilla dimensions.");
}
getData(dim).markedForDeletion = true;
Path base = Paths.get(".").toAbsolutePath().normalize();
Path directory = dim.getDirectory(base.toFile()).toPath().toAbsolutePath().normalize();
if (!directory.startsWith(base))
{
LOGGER.warn("DimensionType {} returned save directory outside of base directory. This is bad. The directory will not be deleted.", dim);
}
else
{
String relative = base.relativize(directory).toString();
foldersScheduledForDeletion.add(relative);
}
}
//==========================================================================================================
// FORGE INTERNAL
//==========================================================================================================
/**
* Deprecated, unregistering dimensions at runtime is not supported.
* @see DimensionManager#markForDeletion(DimensionType)
*/
@Deprecated
public static void unregisterDimension(int id)
{
Validate.isTrue(dimensions.containsKey(id), String.format("Failed to unregister dimension for id %d; No provider registered", id));
dimensions.remove(id);
}
public static DimensionType registerDimensionInternal(int id, ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight)
@ -347,7 +392,7 @@ public class DimensionManager
data.putInt("version", 1);
List<SavedEntry> list = new ArrayList<>();
for (DimensionType type : REGISTRY)
list.add(new SavedEntry(type));
list.add(new SavedEntry(type, getData(type).markedForDeletion));
savedEntries.values().forEach(list::add);
Collections.sort(list, (a, b) -> a.id - b.id);
@ -355,6 +400,10 @@ public class DimensionManager
list.forEach(e -> lst.add(e.write()));
data.put("entries", lst);
ListNBT deleteLst = new ListNBT();
foldersScheduledForDeletion.forEach(folder -> deleteLst.add(StringNBT.valueOf(folder)));
data.put("delete_dirs", deleteLst);
}
public static void readRegistry(CompoundNBT data)
@ -396,7 +445,7 @@ public class DimensionManager
continue;
}
}
else
else if (!entry.markedForDeletion)
{
ModDimension mod = ForgeRegistries.MOD_DIMENSIONS.getValue(entry.type);
if (mod == null)
@ -408,6 +457,46 @@ public class DimensionManager
registerDimensionInternal(entry.id, entry.name, mod, entry.data == null ? null : new PacketBuffer(Unpooled.wrappedBuffer(entry.data)), entry.skyLight());
}
}
foldersScheduledForDeletion.clear();
ListNBT folderList = data.getList("delete_dirs", Constants.NBT.TAG_STRING);
folderList.stream()
.map(INBT::getString)
.forEach(foldersScheduledForDeletion::add);
}
public static void processScheduledDeletions(SaveHandler saveHandler)
{
List<String> toDelete = new ArrayList<>(foldersScheduledForDeletion);
foldersScheduledForDeletion.clear();
if (!toDelete.isEmpty())
{
StringBuilder text = new StringBuilder();
text.append("The following dimensions are marked for deletion by their owning mod. Proceed?\n\n");
for (String folder : toDelete)
{
text.append(folder).append('\n');
}
if (!StartupQuery.confirm(text.toString()))
{
StartupQuery.abort();
return;
}
}
for (String folderName : toDelete)
{
File folder = new File(saveHandler.getWorldDirectory(), folderName);
try
{
FileUtils.deleteDirectory(folder);
LOGGER.info(DIMMGR, "Modded dimension directory {} scheduled for deletion was deleted.", folderName);
}
catch (Exception e)
{
LOGGER.error(DIMMGR, "Failed to delete modded dimension directory {} that was scheduled for deletion.", folderName, e);
}
}
}
public static void fireRegister()
@ -436,6 +525,7 @@ public class DimensionManager
{
int ticksWaited = 0;
boolean keepLoaded = false;
boolean markedForDeletion = false;
}
public static class SavedEntry
@ -445,6 +535,7 @@ public class DimensionManager
ResourceLocation type;
byte[] data;
boolean skyLight;
boolean markedForDeletion;
public int getId()
{
@ -480,9 +571,10 @@ public class DimensionManager
this.type = data.contains("type", 8) ? new ResourceLocation(data.getString("type")) : null;
this.data = data.contains("data", 7) ? data.getByteArray("data") : null;
this.skyLight = data.contains("sky_light", 99) ? data.getBoolean("sky_light") : true;
this.markedForDeletion = data.getBoolean("marked_for_deletion");
}
private SavedEntry(DimensionType data)
private SavedEntry(DimensionType data, boolean markedForDeletion)
{
this.id = REGISTRY.getId(data);
this.name = REGISTRY.getKey(data);
@ -491,6 +583,7 @@ public class DimensionManager
if (data.getData() != null)
this.data = data.getData().array();
this.skyLight = data.hasSkyLight();
this.markedForDeletion = markedForDeletion;
}
private CompoundNBT write()
@ -503,6 +596,7 @@ public class DimensionManager
if (data != null)
ret.putByteArray("data", data);
ret.putBoolean("sky_light", skyLight);
ret.putBoolean("marked_for_deletion", markedForDeletion);
return ret;
}
}

View file

@ -158,6 +158,7 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook
{
if (tag.contains("dims", 10))
DimensionManager.readRegistry(tag.getCompound("dims"));
DimensionManager.processScheduledDeletions(handler);
}
public void mappingChanged(FMLModIdMappingEvent evt)

View file

@ -0,0 +1,186 @@
package net.minecraftforge.debug.world;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.dimension.OverworldDimension;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.ModDimension;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.ITeleporter;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
import java.util.function.Function;
@Mod(MarkDimensionForDeletionTest.MODID)
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = MarkDimensionForDeletionTest.MODID)
public class MarkDimensionForDeletionTest
{
public static final String MODID = "mark_dimension_for_deletion_test";
private static final DeferredRegister<ModDimension> DIMENSIONS = new DeferredRegister<>(ForgeRegistries.MOD_DIMENSIONS, MODID);
private static final DeferredRegister<Item> ITEMS = new DeferredRegister<>(ForgeRegistries.ITEMS, MODID);
private static final RegistryObject<ModDimension> DYNAMIC_DIMENSION_TYPE = DIMENSIONS.register(
"dynamic_dimension",
() -> ModDimension.withFactory(OverworldDimension::new)
);
private static final RegistryObject<Item> DIM_ITEM = ITEMS.register("dim_item", () -> new Item(new Item.Properties().group(ItemGroup.MISC))
{
@Override
public ActionResult<ItemStack> onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn)
{
if (!worldIn.isRemote)
{
if (playerIn.isShiftKeyDown())
{
playerIn.sendMessage(new StringTextComponent("You are in dimension " + worldIn.dimension.getType().getRegistryName()));
}
else
{
DynamicDimensionCap cap = playerIn.getCapability(CAP).orElseThrow(IllegalStateException::new);
ITeleporter teleporter = new ITeleporter()
{
@Override
public Entity placeEntity(Entity entity, ServerWorld currentWorld, ServerWorld destWorld, float yaw, Function<Boolean, Entity> repositionEntity)
{
return repositionEntity.apply(false);
}
};
if (cap.dimension == null)
{
ResourceLocation dimName = new ResourceLocation(MODID, "dynamic_" + playerIn.getUniqueID().toString() + "_" + UUID.randomUUID().toString());
cap.dimension = DimensionManager.registerDimension(dimName, DYNAMIC_DIMENSION_TYPE.get(), null, true);
DimensionManager.initWorld(worldIn.getServer(), cap.dimension);
playerIn.changeDimension(cap.dimension, teleporter);
}
else if (playerIn.dimension == cap.dimension)
{
playerIn.changeDimension(DimensionType.OVERWORLD, teleporter);
DimensionManager.markForDeletion(cap.dimension);
}
else
{
playerIn.changeDimension(cap.dimension, teleporter);
}
}
}
return ActionResult.resultSuccess(playerIn.getHeldItem(handIn));
}
});
@CapabilityInject(DynamicDimensionCap.class)
public static Capability<DynamicDimensionCap> CAP;
public MarkDimensionForDeletionTest()
{
DIMENSIONS.register(FMLJavaModLoadingContext.get().getModEventBus());
ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus());
}
@SubscribeEvent
public static void commonSetup(FMLCommonSetupEvent event)
{
CapabilityManager.INSTANCE.register(DynamicDimensionCap.class, new DynamicDimensionCap.Storage(), DynamicDimensionCap::new);
}
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE, modid = MODID)
public static class ForgeEventSubscriber
{
@SubscribeEvent
public static void attachCaps(AttachCapabilitiesEvent<Entity> event)
{
if (event.getObject() instanceof ServerPlayerEntity)
{
event.addCapability(new ResourceLocation(MODID, "dynamic_dimension"), new DynamicDimensionCap());
}
}
}
public static class DynamicDimensionCap implements ICapabilitySerializable<CompoundNBT>
{
static class Storage implements Capability.IStorage<DynamicDimensionCap>
{
@Nullable
@Override
public INBT writeNBT(Capability<DynamicDimensionCap> capability, DynamicDimensionCap instance, Direction side)
{
return instance.serializeNBT();
}
@Override
public void readNBT(Capability<DynamicDimensionCap> capability, DynamicDimensionCap instance, Direction side, INBT nbt)
{
instance.deserializeNBT((CompoundNBT) nbt);
}
}
public DimensionType dimension = null;
private final LazyOptional<DynamicDimensionCap> instance = LazyOptional.of(() -> this);
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side)
{
return CAP.orEmpty(cap, instance);
}
@Override
public CompoundNBT serializeNBT()
{
CompoundNBT result = new CompoundNBT();
if (dimension != null && dimension.getRegistryName() != null)
{
result.putString("dimension", dimension.getRegistryName().toString());
}
return result;
}
@Override
public void deserializeNBT(CompoundNBT nbt)
{
if (nbt.contains("dimension", Constants.NBT.TAG_STRING))
{
dimension = DimensionType.byName(new ResourceLocation(nbt.getString("dimension")));
}
else
{
dimension = null;
}
}
}
}

View file

@ -1,6 +1,8 @@
modLoader="javafml"
loaderVersion="[28,)"
[[mods]]
modId="mark_dimension_for_deletion_test"
[[mods]]
modId="containertypetest"
[[mods]]
@ -10,6 +12,7 @@ loaderVersion="[28,)"
[[mods]]
modId="farmland_trample_test"
[[mods]]
modId="neighbor_notify_event_test"
[[mods]]
modId="block_place_event_test"