src: add ChunkUnloadingSystem
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package com.hypixel.hytale.server.core.universe.world.storage.component;
|
||||
|
||||
import com.hypixel.hytale.component.ArchetypeChunk;
|
||||
import com.hypixel.hytale.component.CommandBuffer;
|
||||
import com.hypixel.hytale.component.Ref;
|
||||
import com.hypixel.hytale.component.RemoveReason;
|
||||
import com.hypixel.hytale.component.Resource;
|
||||
import com.hypixel.hytale.component.Store;
|
||||
import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem;
|
||||
import com.hypixel.hytale.component.system.tick.TickingSystem;
|
||||
import com.hypixel.hytale.math.shape.Box2D;
|
||||
import com.hypixel.hytale.math.util.ChunkUtil;
|
||||
import com.hypixel.hytale.math.util.MathUtil;
|
||||
import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker;
|
||||
import com.hypixel.hytale.server.core.universe.world.World;
|
||||
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag;
|
||||
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
|
||||
import com.hypixel.hytale.server.core.universe.world.events.ecs.ChunkUnloadEvent;
|
||||
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
|
||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ChunkUnloadingSystem extends TickingSystem<ChunkStore> implements RunWhenPausedSystem<ChunkStore> {
|
||||
public static final double DESPERATE_UNLOAD_RAM_USAGE_THRESHOLD = 0.85;
|
||||
public static final int DESPERATE_UNLOAD_MAX_POLL_COUNT = 3;
|
||||
public static final int TICKS_BEFORE_CHUNK_UNLOADING_REMINDER = 5000;
|
||||
public int ticksUntilUnloadingReminder = 5000;
|
||||
|
||||
public ChunkUnloadingSystem() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(float dt, int systemIndex, @Nonnull Store<ChunkStore> store) {
|
||||
ChunkUnloadingSystem.Data dataResource = store.getResource(ChunkStore.UNLOAD_RESOURCE);
|
||||
World world = store.getExternalData().getWorld();
|
||||
if (!world.getWorldConfig().canUnloadChunks()) {
|
||||
this.ticksUntilUnloadingReminder--;
|
||||
if (this.ticksUntilUnloadingReminder <= 0) {
|
||||
world.getLogger().at(Level.INFO).log("This world has disabled chunk unloading");
|
||||
this.ticksUntilUnloadingReminder = 5000;
|
||||
}
|
||||
} else {
|
||||
int pollCount = 1;
|
||||
double percentOfRAMUsed = 1.0 - (double) Runtime.getRuntime().freeMemory() / Runtime.getRuntime().maxMemory();
|
||||
if (percentOfRAMUsed > 0.85) {
|
||||
double desperatePercent = (percentOfRAMUsed - 0.85) / 0.15000000000000002;
|
||||
pollCount = Math.max(MathUtil.ceil(desperatePercent * 3.0), 1);
|
||||
}
|
||||
|
||||
dataResource.pollCount = pollCount;
|
||||
if (dataResource.tick(dt)) {
|
||||
dataResource.chunkTrackers.clear();
|
||||
world.getEntityStore().getStore().forEachChunk(ChunkTracker.getComponentType(), ChunkUnloadingSystem::collectTrackers);
|
||||
store.forEachEntityParallel(WorldChunk.getComponentType(), ChunkUnloadingSystem::tryUnload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryUnload(int index, @Nonnull ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull CommandBuffer<ChunkStore> commandBuffer) {
|
||||
Store<ChunkStore> store = commandBuffer.getStore();
|
||||
World world = store.getExternalData().getWorld();
|
||||
WorldChunk worldChunkComponent = archetypeChunk.getComponent(index, WorldChunk.getComponentType());
|
||||
|
||||
assert worldChunkComponent != null;
|
||||
|
||||
ChunkUnloadingSystem.Data dataResource = commandBuffer.getResource(ChunkStore.UNLOAD_RESOURCE);
|
||||
ChunkTracker.ChunkVisibility chunkVisibility = getChunkVisibility(dataResource.chunkTrackers, worldChunkComponent.getIndex());
|
||||
if (chunkVisibility == ChunkTracker.ChunkVisibility.HOT) {
|
||||
worldChunkComponent.resetKeepAlive();
|
||||
worldChunkComponent.resetActiveTimer();
|
||||
} else {
|
||||
Box2D keepLoaded = world.getWorldConfig().getChunkConfig().getKeepLoadedRegion();
|
||||
boolean shouldKeepLoaded = worldChunkComponent.shouldKeepLoaded()
|
||||
|| keepLoaded != null && isChunkInBox(keepLoaded, worldChunkComponent.getX(), worldChunkComponent.getZ());
|
||||
int pollCount = dataResource.pollCount;
|
||||
if (chunkVisibility == ChunkTracker.ChunkVisibility.COLD || worldChunkComponent.getNeedsSaving() || shouldKeepLoaded) {
|
||||
worldChunkComponent.resetKeepAlive();
|
||||
if (worldChunkComponent.is(ChunkFlag.TICKING) && worldChunkComponent.pollActiveTimer(pollCount) <= 0) {
|
||||
commandBuffer.run(s -> worldChunkComponent.setFlag(ChunkFlag.TICKING, false));
|
||||
}
|
||||
} else if (worldChunkComponent.pollKeepAlive(pollCount) <= 0) {
|
||||
Ref<ChunkStore> chunkRef = archetypeChunk.getReferenceTo(index);
|
||||
ChunkUnloadEvent event = new ChunkUnloadEvent(worldChunkComponent);
|
||||
commandBuffer.invoke(chunkRef, event);
|
||||
if (event.isCancelled()) {
|
||||
if (event.willResetKeepAlive()) {
|
||||
worldChunkComponent.resetKeepAlive();
|
||||
}
|
||||
} else {
|
||||
commandBuffer.run(s -> s.getExternalData().remove(chunkRef, RemoveReason.UNLOAD));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ChunkTracker.ChunkVisibility getChunkVisibility(@Nonnull List<ChunkTracker> playerChunkTrackers, long chunkIndex) {
|
||||
boolean isVisible = false;
|
||||
|
||||
for (ChunkTracker chunkTracker : playerChunkTrackers) {
|
||||
switch (chunkTracker.getChunkVisibility(chunkIndex)) {
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
case HOT:
|
||||
return ChunkTracker.ChunkVisibility.HOT;
|
||||
case COLD:
|
||||
isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isVisible ? ChunkTracker.ChunkVisibility.COLD : ChunkTracker.ChunkVisibility.NONE;
|
||||
}
|
||||
|
||||
private static boolean isChunkInBox(@Nonnull Box2D box, int x, int z) {
|
||||
int minX = ChunkUtil.minBlock(x);
|
||||
int minZ = ChunkUtil.minBlock(z);
|
||||
int maxX = ChunkUtil.maxBlock(x);
|
||||
int maxZ = ChunkUtil.maxBlock(z);
|
||||
return maxX >= box.min.x && minX <= box.max.x && maxZ >= box.min.y && minZ <= box.max.y;
|
||||
}
|
||||
|
||||
private static void collectTrackers(@Nonnull ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
|
||||
Store<ChunkStore> chunkStore = commandBuffer.getExternalData().getWorld().getChunkStore().getStore();
|
||||
ChunkUnloadingSystem.Data dataResource = chunkStore.getResource(ChunkStore.UNLOAD_RESOURCE);
|
||||
|
||||
for (int index = 0; index < archetypeChunk.size(); index++) {
|
||||
ChunkTracker chunkTracker = archetypeChunk.getComponent(index, ChunkTracker.getComponentType());
|
||||
dataResource.chunkTrackers.add(chunkTracker);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data implements Resource<ChunkStore> {
|
||||
public static final float UNLOAD_INTERVAL = 0.5F;
|
||||
private float time;
|
||||
private int pollCount = 1;
|
||||
@Nonnull
|
||||
private final List<ChunkTracker> chunkTrackers = new ObjectArrayList<>();
|
||||
|
||||
public Data() {
|
||||
this.time = 0.5F;
|
||||
}
|
||||
|
||||
public Data(float time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Resource<ChunkStore> clone() {
|
||||
return new ChunkUnloadingSystem.Data(this.time);
|
||||
}
|
||||
|
||||
public boolean tick(float dt) {
|
||||
this.time -= dt;
|
||||
if (this.time <= 0.0F) {
|
||||
this.time += 0.5F;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user