Computer tiles now also get cleaned up properly on world unload.

Needed to properly unload all devices, always. Avoids warnings of leaked allocation on shutdown, but more importantly avoids memory leaking when running a multi-world game where worlds may repeatedly be loaded and unloaded.
This commit is contained in:
Florian Nücke
2020-12-20 20:18:53 +01:00
parent f641b7b60c
commit e7892efca8
2 changed files with 101 additions and 14 deletions

View File

@@ -81,6 +81,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
///////////////////////////////////////////////////////////////////
private final Runnable onWorldUnloaded = this::onWorldUnloaded;
private Chunk chunk;
private final AbstractDeviceBusController busController;
private AbstractDeviceBusController.BusState busState;
@@ -363,6 +364,9 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
protected void initializeServer() {
super.initializeServer();
final World world = requireNonNull(getWorld());
ServerScheduler.scheduleOnUnload(world, onWorldUnloaded);
busElement.initialize();
virtualMachine.rtc.setWorld(requireNonNull(getWorld()));
ServerScheduler.schedule(() -> chunk = requireNonNull(getWorld()).getChunkAt(getPos()));
@@ -372,6 +376,8 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
protected void disposeServer() {
super.disposeServer();
ServerScheduler.removeOnUnload(getWorld(), onWorldUnloaded);
stopRunnerAndResetVM();
busController.dispose();
busElement.dispose();
@@ -426,6 +432,10 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
MemoryMaps.store(virtualMachine.board.getMemoryMap(), 0x80000000L + offset, stream);
}
private void onWorldUnloaded() {
disposeServer();
}
///////////////////////////////////////////////////////////////////
private final class BusController extends AbstractDeviceBusController {

View File

@@ -1,19 +1,24 @@
package li.cil.oc2.common.util;
import net.minecraft.world.IWorld;
import net.minecraft.world.chunk.IChunk;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.PriorityQueue;
import java.util.WeakHashMap;
import java.util.*;
import java.util.stream.Collectors;
public final class ServerScheduler {
private static final Scheduler serverScheduler = new Scheduler();
private static final WeakHashMap<IWorld, Scheduler> worldSchedulers = new WeakHashMap<>();
private static final TickScheduler globalTickScheduler = new TickScheduler();
private static final WeakHashMap<IWorld, TickScheduler> worldTickSchedulers = new WeakHashMap<>();
private static final WeakHashMap<IWorld, UnloadScheduler> worldUnloadSchedulers = new WeakHashMap<>();
private static final WeakHashMap<IChunk, UnloadScheduler> chunkUnloadSchedulers = new WeakHashMap<>();
///////////////////////////////////////////////////////////////////
@@ -26,7 +31,7 @@ public final class ServerScheduler {
}
public static void schedule(final Runnable runnable, final int afterTicks) {
serverScheduler.schedule(runnable, afterTicks);
globalTickScheduler.schedule(runnable, afterTicks);
}
public static void schedule(final IWorld world, final Runnable runnable) {
@@ -34,30 +39,82 @@ public final class ServerScheduler {
}
public static void schedule(final IWorld world, final Runnable runnable, final int afterTicks) {
final Scheduler scheduler = worldSchedulers.computeIfAbsent(world, w -> new Scheduler());
final TickScheduler scheduler = worldTickSchedulers.computeIfAbsent(world, w -> new TickScheduler());
scheduler.schedule(runnable, afterTicks);
}
public static void scheduleOnUnload(final IWorld world, final Runnable listener) {
worldUnloadSchedulers.computeIfAbsent(world, unused -> new UnloadScheduler()).add(listener);
}
public static void removeOnUnload(@Nullable final IWorld world, final Runnable listener) {
if (world == null) {
return;
}
final UnloadScheduler scheduler = worldUnloadSchedulers.get(world);
if (scheduler != null) {
scheduler.remove(listener);
}
}
public static void scheduleOnUnload(final IChunk chunk, final Runnable listener) {
chunkUnloadSchedulers.computeIfAbsent(chunk, unused -> new UnloadScheduler()).add(listener);
}
public static void removeOnUnload(@Nullable final IChunk chunk, final Runnable listener) {
if (chunk == null) {
return;
}
final UnloadScheduler scheduler = chunkUnloadSchedulers.get(chunk);
if (scheduler != null) {
scheduler.remove(listener);
}
}
///////////////////////////////////////////////////////////////////
private static final class EventHandler {
@SubscribeEvent
public static void handleServerStoppedEvent(final FMLServerStoppedEvent event) {
serverScheduler.clear();
worldSchedulers.clear();
globalTickScheduler.clear();
worldTickSchedulers.clear();
worldUnloadSchedulers.clear();
chunkUnloadSchedulers.clear();
}
@SubscribeEvent
public static void handleWorldUnload(final WorldEvent.Unload event) {
worldSchedulers.remove(event.getWorld());
worldTickSchedulers.remove(event.getWorld());
final List<IChunk> unloadedChunks = chunkUnloadSchedulers.keySet().stream()
.filter(chunk -> chunk.getWorldForge() == event.getWorld())
.collect(Collectors.toList());
for (final IChunk chunk : unloadedChunks) {
chunkUnloadSchedulers.remove(chunk);
}
final UnloadScheduler scheduler = worldUnloadSchedulers.remove(event.getWorld());
if (scheduler != null) {
scheduler.run();
}
}
@SubscribeEvent
public static void handleChunkUnload(final ChunkEvent.Unload event) {
final UnloadScheduler scheduler = chunkUnloadSchedulers.remove(event.getChunk());
if (scheduler != null) {
scheduler.run();
}
}
@SubscribeEvent
public static void handleServerTick(final TickEvent.ServerTickEvent event) {
if (event.phase == TickEvent.Phase.START) {
serverScheduler.tick();
globalTickScheduler.tick();
for (final Scheduler scheduler : worldSchedulers.values()) {
for (final TickScheduler scheduler : worldTickSchedulers.values()) {
scheduler.tick();
}
}
@@ -69,16 +126,16 @@ public final class ServerScheduler {
return;
}
serverScheduler.processQueue();
globalTickScheduler.processQueue();
final Scheduler scheduler = worldSchedulers.get(event.world);
final TickScheduler scheduler = worldTickSchedulers.get(event.world);
if (scheduler != null) {
scheduler.processQueue();
}
}
}
private static final class Scheduler {
private static final class TickScheduler {
private final PriorityQueue<ScheduledRunnable> queue = new PriorityQueue<>();
private int currentTick;
@@ -116,4 +173,24 @@ public final class ServerScheduler {
return Integer.compare(tick, o.tick);
}
}
private static final class UnloadScheduler {
private final Set<Runnable> listeners = Collections.newSetFromMap(new WeakHashMap<>());
public void add(final Runnable listener) {
listeners.add(listener);
}
public void remove(final Runnable listener) {
listeners.remove(listener);
}
public void run() {
for (final Runnable runnable : listeners) {
runnable.run();
}
listeners.clear();
}
}
}