Skip to content

Commit

Permalink
Backport: Add support for world unloading, fix world data being kept …
Browse files Browse the repository at this point in the history
…in level.dat (again), fix random crashes when adding/removing a world
  • Loading branch information
pisaiah committed Aug 9, 2023
1 parent 7706644 commit 9338490
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 9 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ yarn_mappings=1.19+build.1
loader_version=0.14.9

# Mod Properties
mod_version=0.4.9
mod_version=0.4.10
maven_group=xyz.nucleoid
archives_base_name=fantasy

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/xyz/nucleoid/fantasy/Fantasy.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public final class Fantasy {
private final RuntimeWorldManager worldManager;

private final Set<ServerWorld> deletionQueue = new ReferenceOpenHashSet<>();
private final Set<ServerWorld> unloadingQueue = new ReferenceOpenHashSet<>();

static {
ServerTickEvents.START_SERVER_TICK.register(server -> {
Expand Down Expand Up @@ -88,6 +89,11 @@ private void tick() {
if (!deletionQueue.isEmpty()) {
deletionQueue.removeIf(this::tickDeleteWorld);
}

Set<ServerWorld> unloadingQueue = this.unloadingQueue;
if (!unloadingQueue.isEmpty()) {
unloadingQueue.removeIf(this::tickUnloadWorld);
}
}

/**
Expand Down Expand Up @@ -159,6 +165,12 @@ void enqueueWorldDeletion(ServerWorld world) {
});
}

void enqueueWorldUnloading(ServerWorld world) {
this.server.submit(() -> {
this.unloadingQueue.add(world);
});
}

private boolean tickDeleteWorld(ServerWorld world) {
if (this.isWorldUnloaded(world)) {
this.worldManager.delete(world);
Expand All @@ -169,6 +181,16 @@ private boolean tickDeleteWorld(ServerWorld world) {
}
}

private boolean tickUnloadWorld(ServerWorld world) {
if (this.isWorldUnloaded(world)) {
this.worldManager.unload(world);
return true;
} else {
this.kickPlayers(world);
return false;
}
}

private void kickPlayers(ServerWorld world) {
if (world.getPlayers().isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

public interface FantasyDimensionOptions {
Predicate<DimensionOptions> SAVE_PREDICATE = (e) -> ((FantasyDimensionOptions) (Object) e).fantasy$getSave();
Predicate<DimensionOptions> SAVE_PROPERTIES_PREDICATE = (e) -> ((FantasyDimensionOptions) (Object) e).fantasy$getSaveProperties();

void fantasy$setSave(boolean value);
boolean fantasy$getSave();
void fantasy$setSaveProperties(boolean value);
boolean fantasy$getSaveProperties();
}
7 changes: 7 additions & 0 deletions src/main/java/xyz/nucleoid/fantasy/RuntimeWorldHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public void delete() {
this.fantasy.enqueueWorldDeletion(this.world);
}

/**
* Unloads the world. It only deletes the files if world is temporary.
*/
public void unload() {
this.fantasy.enqueueWorldUnloading(this.world);
}

public ServerWorld asWorld() {
return this.world;
}
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/xyz/nucleoid/fantasy/RuntimeWorldManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.ProgressListener;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.util.registry.SimpleRegistry;
Expand Down Expand Up @@ -32,6 +34,7 @@ RuntimeWorld add(RegistryKey<World> worldKey, RuntimeWorldConfig config, Runtime
if (style == RuntimeWorld.Style.TEMPORARY) {
((FantasyDimensionOptions) (Object) options).fantasy$setSave(false);
}
((FantasyDimensionOptions) (Object) options).fantasy$setSaveProperties(false);

SimpleRegistry<DimensionOptions> dimensionsRegistry = getDimensionsRegistry(this.server);
boolean isFrozen = ((RemoveFromRegistry<?>) dimensionsRegistry).fantasy$isFrozen();
Expand Down Expand Up @@ -78,6 +81,34 @@ void delete(ServerWorld world) {
}
}

void unload(ServerWorld world) {
RegistryKey<World> dimensionKey = world.getRegistryKey();

if (this.serverAccess.getWorlds().remove(dimensionKey, world)) {
world.save(new ProgressListener() {
@Override
public void setTitle(Text title) {}

@Override
public void setTitleAndTask(Text title) {}

@Override
public void setTask(Text task) {}

@Override
public void progressStagePercentage(int percentage) {}

@Override
public void setDone() {
ServerWorldEvents.UNLOAD.invoker().onWorldUnload(RuntimeWorldManager.this.server, world);

SimpleRegistry<DimensionOptions> dimensionsRegistry = getDimensionsRegistry(RuntimeWorldManager.this.server);
RemoveFromRegistry.remove(dimensionsRegistry, dimensionKey.getValue());
}
}, true, false);
}
}

private static SimpleRegistry<DimensionOptions> getDimensionsRegistry(MinecraftServer server) {
GeneratorOptions generatorOptions = server.getSaveProperties().getGeneratorOptions();
return (SimpleRegistry<DimensionOptions>) generatorOptions.getDimensions();
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/xyz/nucleoid/fantasy/mixin/MinecraftServerMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package xyz.nucleoid.fantasy.mixin;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import xyz.nucleoid.fantasy.util.SafeIterator;

import java.util.Collection;
import java.util.Iterator;

@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@Redirect(method = "tickWorlds", at = @At(value = "INVOKE", target = "Ljava/lang/Iterable;iterator()Ljava/util/Iterator;", ordinal = 0), require = 0)
private Iterator<ServerWorld> fantasy$copyBeforeTicking(Iterable<ServerWorld> instance) {
return new SafeIterator<>((Collection<ServerWorld>) instance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
public class DimensionOptionsMixin implements FantasyDimensionOptions {
@Unique
private boolean fantasy$save = true;
@Unique
private boolean fantasy$saveProperties = true;

@Override
public void fantasy$setSave(boolean value) {
Expand All @@ -19,4 +21,14 @@ public class DimensionOptionsMixin implements FantasyDimensionOptions {
public boolean fantasy$getSave() {
return this.fantasy$save;
}

@Override
public void fantasy$setSaveProperties(boolean value) {
this.fantasy$saveProperties = value;
}

@Override
public boolean fantasy$getSaveProperties() {
return this.fantasy$saveProperties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package xyz.nucleoid.fantasy.mixin.registry;

// import net.minecraft.registry.Registry;
import net.minecraft.world.dimension.DimensionOptions;
// import net.minecraft.world.dimension.DimensionOptionsRegistryHolder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import xyz.nucleoid.fantasy.FantasyDimensionOptions;
import xyz.nucleoid.fantasy.util.FilteredRegistry;

import java.util.function.Function;

// @Mixin(DimensionOptionsRegistryHolder.class)
public class DimensionOptionsRegistryHolderMixin {
//@ModifyArg(method = "method_45516", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/MapCodec;forGetter(Ljava/util/function/Function;)Lcom/mojang/serialization/codecs/RecordCodecBuilder;"))
//private static Function<Object, Registry<DimensionOptions>> fantasy$swapRegistryGetter(Function<Object, Registry<DimensionOptions>> getter) {
// return (x) -> new FilteredRegistry<>(getter.apply(x), FantasyDimensionOptions.SAVE_PROPERTIES_PREDICATE);
//}
}
23 changes: 23 additions & 0 deletions src/main/java/xyz/nucleoid/fantasy/util/SafeIterator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.nucleoid.fantasy.util;

import java.util.Collection;
import java.util.Iterator;

public final class SafeIterator<T> implements Iterator<T> {
private final Object[] values;
private int index = 0;

public SafeIterator(Collection<T> source) {
this.values = source.toArray();
}

@Override
public boolean hasNext() {
return this.values.length > this.index;
}

@Override
public T next() {
return (T) this.values[this.index++];
}
}
1 change: 1 addition & 0 deletions src/main/resources/fantasy.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"compatibilityLevel": "JAVA_17",
"mixins": [
"MinecraftServerAccess",
"MinecraftServerMixin",
"ServerChunkManagerMixin",
"ServerWorldMixin",
"registry.DimensionOptionsMixin",
Expand Down
81 changes: 73 additions & 8 deletions src/testmod/java/xyz/nucleoid/fantasy/test/FantasyInitializer.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,92 @@
package xyz.nucleoid.fantasy.test;

import com.mojang.brigadier.CommandDispatcher;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.TeleportTarget;
import xyz.nucleoid.fantasy.Fantasy;
import xyz.nucleoid.fantasy.RuntimeWorldConfig;
import xyz.nucleoid.fantasy.RuntimeWorldHandle;
import xyz.nucleoid.fantasy.util.VoidChunkGenerator;

import java.util.HashMap;
import java.util.WeakHashMap;

import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;

public final class FantasyInitializer implements ModInitializer {
private HashMap<Identifier, RuntimeWorldHandle> worlds = new HashMap<>();

@Override
public void onInitialize() {
ServerLifecycleEvents.SERVER_STARTED.register((s) -> {
Fantasy.get(s).openTemporaryWorld(new RuntimeWorldConfig().setGenerator(new VoidChunkGenerator(s.getRegistryManager().get(Registry.BIOME_KEY).getEntry(0).get())));
Fantasy.get(s).getOrOpenPersistentWorld(
new Identifier("fantasytest:test"),
new RuntimeWorldConfig()
.setGenerator(s.getOverworld().getChunkManager().getChunkGenerator())
);
});
}

CommandRegistrationCallback.EVENT.register(((dispatcher, registryAccess, environment) -> {
dispatcher.register(literal("fantasy_open").then(
argument("name", IdentifierArgumentType.identifier())
.executes((context -> {
try {
var t = System.currentTimeMillis();
var id = IdentifierArgumentType.getIdentifier(context, "name");

var x = Fantasy.get(context.getSource().getServer()).getOrOpenPersistentWorld(
id,
new RuntimeWorldConfig()
.setGenerator(context.getSource().getServer().getOverworld().getChunkManager().getChunkGenerator())
.setSeed(id.hashCode())
);
context.getSource().sendFeedback(Text.literal("WorldCreate: " + (System.currentTimeMillis() - t)), false);

worlds.put(id, x);
t = System.currentTimeMillis();
FabricDimensions.teleport(context.getSource().getEntity(), x.asWorld(), new TeleportTarget(new Vec3d(0, 100 ,0) , Vec3d.ZERO, 0, 0));
context.getSource().sendFeedback(Text.literal("Teleport: " + (System.currentTimeMillis() - t)), false);
} catch (Throwable e) {
e.printStackTrace();
}

return 0;
}))
));

dispatcher.register(literal("fantasy_delete").then(
argument("name", IdentifierArgumentType.identifier())
.executes((context -> {
try {
var id = IdentifierArgumentType.getIdentifier(context, "name");
worlds.get(id).delete();
worlds.remove(id);
} catch (Throwable e) {
e.printStackTrace();
}
return 0;
}))
));

dispatcher.register(literal("fantasy_unload").then(
argument("name", IdentifierArgumentType.identifier())
.executes((context -> {
try {
var id = IdentifierArgumentType.getIdentifier(context, "name");
worlds.get(id).unload();
worlds.remove(id);
} catch (Throwable e) {
e.printStackTrace();
}

return 0;
}))
));
}));
}
}

0 comments on commit 9338490

Please sign in to comment.