diff --git a/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java index e7d01e50..cfdbe73a 100644 --- a/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java @@ -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 { diff --git a/src/main/java/li/cil/oc2/common/util/ServerScheduler.java b/src/main/java/li/cil/oc2/common/util/ServerScheduler.java index 095e6823..930bf0d2 100644 --- a/src/main/java/li/cil/oc2/common/util/ServerScheduler.java +++ b/src/main/java/li/cil/oc2/common/util/ServerScheduler.java @@ -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 worldSchedulers = new WeakHashMap<>(); + private static final TickScheduler globalTickScheduler = new TickScheduler(); + private static final WeakHashMap worldTickSchedulers = new WeakHashMap<>(); + private static final WeakHashMap worldUnloadSchedulers = new WeakHashMap<>(); + private static final WeakHashMap 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 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 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 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(); + } + } }