mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-01 15:36:45 +00:00
fix deadlock when hotloading / closing engine
This commit is contained in:
parent
9e0258089b
commit
9b96b4d619
@ -1,5 +1,6 @@
|
|||||||
package com.volmit.iris.engine.service;
|
package com.volmit.iris.engine.service;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.AtomicDouble;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.IrisSettings;
|
import com.volmit.iris.core.IrisSettings;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
@ -23,6 +24,7 @@ import io.papermc.lib.PaperLib;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.bukkit.ChunkSnapshot;
|
import org.bukkit.ChunkSnapshot;
|
||||||
import org.bukkit.GameRule;
|
import org.bukkit.GameRule;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@ -37,6 +39,7 @@ import java.util.Set;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -50,10 +53,12 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
private final AtomicLong currentTick = new AtomicLong();
|
private final AtomicLong currentTick = new AtomicLong();
|
||||||
private final Sync<Long> sync = new Sync<>();
|
private final Sync<Long> sync = new Sync<>();
|
||||||
private final Set<Player> players = ConcurrentHashMap.newKeySet();
|
private final Set<Player> players = ConcurrentHashMap.newKeySet();
|
||||||
private KList<Entity> entities = new KList<>();
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
private Thread asyncTicker = null;
|
private transient KList<Entity> entities = new KList<>();
|
||||||
private Thread entityCollector = null;
|
private transient Thread asyncTicker = null;
|
||||||
private int task = -1;
|
private transient Thread entityCollector = null;
|
||||||
|
private transient boolean charge = false;
|
||||||
|
private transient int task = -1;
|
||||||
|
|
||||||
public EngineMobHandlerSVC(Engine engine) {
|
public EngineMobHandlerSVC(Engine engine) {
|
||||||
super(engine);
|
super(engine);
|
||||||
@ -61,22 +66,24 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable(boolean hotload) {
|
public void onEnable(boolean hotload) {
|
||||||
if (task != -1) J.csr(task);
|
if (running.get()) {
|
||||||
task = J.sr(() -> sync.advance(currentTick.getAndIncrement()), 0);
|
running.set(false);
|
||||||
|
cancel(asyncTicker);
|
||||||
|
cancel(entityCollector);
|
||||||
|
}
|
||||||
|
|
||||||
cancel(asyncTicker);
|
running.set(true);
|
||||||
cancel(entityCollector);
|
charge = hotload;
|
||||||
asyncTicker = Thread.ofPlatform()
|
asyncTicker = Thread.ofPlatform()
|
||||||
.name("Iris Async Mob Spawning - " + engine.getWorld().name())
|
.name("Iris Async Mob Spawning - " + engine.getWorld().name())
|
||||||
.priority(9)
|
.priority(9)
|
||||||
.start(() -> {
|
.start(() -> {
|
||||||
while (!engine.isClosed()) {
|
while (mayLoop()) {
|
||||||
if (Thread.interrupted())
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
asyncTick();
|
asyncTick();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
if (isInterrupted(e))
|
||||||
|
return;
|
||||||
Iris.error("Error in async tick for " + engine.getWorld().name());
|
Iris.error("Error in async tick for " + engine.getWorld().name());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
@ -87,16 +94,16 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
entityCollector = Thread.ofVirtual()
|
entityCollector = Thread.ofVirtual()
|
||||||
.name("Iris Async Entity Collector - " + engine.getWorld().name())
|
.name("Iris Async Entity Collector - " + engine.getWorld().name())
|
||||||
.start(() -> {
|
.start(() -> {
|
||||||
while (!engine.isClosed()) {
|
while (mayLoop()) {
|
||||||
if (Thread.interrupted())
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sync.next().join();
|
sync.next().get();
|
||||||
var world = engine.getWorld().realWorld();
|
var world = engine.getWorld().realWorld();
|
||||||
if (world == null) continue;
|
if (world == null) continue;
|
||||||
J.s(() -> entities = new KList<>(world.getEntities()));
|
J.s(() -> entities = new KList<>(world.getEntities()));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
if (isInterrupted(e))
|
||||||
|
return;
|
||||||
|
|
||||||
Iris.error("Error in async tick for " + engine.getWorld().name());
|
Iris.error("Error in async tick for " + engine.getWorld().name());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
@ -104,19 +111,26 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (task != -1) J.csr(task);
|
||||||
|
task = J.sr(() -> sync.advance(currentTick.getAndIncrement()), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable(boolean hotload) {
|
public void onDisable(boolean hotload) {
|
||||||
J.csr(task);
|
running.set(false);
|
||||||
cancel(asyncTicker);
|
cancel(asyncTicker);
|
||||||
cancel(entityCollector);
|
cancel(entityCollector);
|
||||||
|
if (!hotload) J.csr(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
private void asyncTick() throws Throwable {
|
||||||
private void asyncTick() {
|
long tick = sync.next().get();
|
||||||
long tick = sync.next().join();
|
|
||||||
var manager = (IrisWorldManager) engine.getWorldManager();
|
var manager = (IrisWorldManager) engine.getWorldManager();
|
||||||
|
if (charge) {
|
||||||
|
manager.chargeEnergy();
|
||||||
|
charge = false;
|
||||||
|
}
|
||||||
|
|
||||||
var world = engine.getWorld().realWorld();
|
var world = engine.getWorld().realWorld();
|
||||||
if (world == null
|
if (world == null
|
||||||
|| noSpawning()
|
|| noSpawning()
|
||||||
@ -133,7 +147,7 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
var invalid = data.getSpawnerLoader()
|
var invalid = data.getSpawnerLoader()
|
||||||
.streamAllPossible()
|
.streamAllPossible()
|
||||||
.filter(Predicate.not(spawner -> spawner.canSpawn(engine)
|
.filter(Predicate.not(spawner -> spawner.canSpawn(engine)
|
||||||
&& spawner.getConditions().check(conditionCache, entities)))
|
&& spawner.getConditions().check(conditionCache, entities)))
|
||||||
.map(IrisSpawner::getLoadKey)
|
.map(IrisSpawner::getLoadKey)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
@ -148,70 +162,77 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
if (centers.isEmpty())
|
if (centers.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double delta = 0;
|
AtomicDouble delta = new AtomicDouble();
|
||||||
int actuallySpawned = 0;
|
int actuallySpawned = 0;
|
||||||
|
|
||||||
KMap<Position2, Pair<Entity[], ChunkSnapshot>> cache = new KMap<>();
|
KMap<Position2, Pair<Entity[], ChunkSnapshot>> cache = new KMap<>();
|
||||||
while (centers.isNotEmpty()) {
|
while (centers.isNotEmpty()) {
|
||||||
var center = centers.pop();
|
var center = centers.pop();
|
||||||
var pos = center.randomPoint(MAX_RADIUS, SAFE_RADIUS);
|
var spawned = trySpawn(world, invalid, cache, center, delta);
|
||||||
if (pos.getY() < world.getMinHeight() || pos.getY() >= world.getMaxHeight())
|
if (spawned == 0 && p.getMilliseconds() < 1000)
|
||||||
continue;
|
|
||||||
|
|
||||||
var chunkPos = new Position2(center.getX() >> 4, center.getZ() >> 4);
|
|
||||||
var pair = cache.computeIfAbsent(chunkPos, cPos -> {
|
|
||||||
try {
|
|
||||||
return PaperLib.getChunkAtAsync(world, cPos.getX(), cPos.getZ(), false)
|
|
||||||
.thenApply(c -> c != null ? new Pair<>(c.getEntities(), c.getChunkSnapshot(false, false, false)) : null)
|
|
||||||
.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (pair == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var spawners = spawnersAt(pair.getB(), pos, invalid);
|
|
||||||
spawners.removeIf(i -> invalid.contains(i.getLoadKey()));
|
|
||||||
spawners.removeIf(i -> !i.canSpawn(engine, chunkPos.getX(), chunkPos.getZ()));
|
|
||||||
|
|
||||||
if (spawners.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
boolean failed = true;
|
|
||||||
IrisPosition irisPos = new IrisPosition(pos.getX(), pos.getY(), pos.getZ());
|
|
||||||
for (var spawner : spawners) {
|
|
||||||
var spawns = spawner.getSpawns().copy();
|
|
||||||
spawns.removeIf(spawn -> !spawn.check(engine, irisPos, pair.getB()));
|
|
||||||
|
|
||||||
var entity = IRare.pick(spawns, RNG.r.nextDouble());
|
|
||||||
if (entity == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
entity.setReferenceSpawner(spawner);
|
|
||||||
entity.setReferenceMarker(spawner.getReferenceMarker());
|
|
||||||
int spawned = entity.spawn(engine, irisPos, RNG.r);
|
|
||||||
if (spawned == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
delta += spawned * ((entity.getEnergyMultiplier() * spawner.getEnergyMultiplier() * 1));
|
|
||||||
actuallySpawned += spawned;
|
|
||||||
|
|
||||||
spawner.spawn(engine, chunkPos.getX(), chunkPos.getZ());
|
|
||||||
if (!spawner.canSpawn(engine))
|
|
||||||
invalid.add(spawner.getLoadKey());
|
|
||||||
failed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (failed && p.getMilliseconds() < 1000)
|
|
||||||
centers.add(center);
|
centers.add(center);
|
||||||
|
actuallySpawned += spawned;
|
||||||
}
|
}
|
||||||
manager.setEnergy(manager.getEnergy() - delta);
|
manager.setEnergy(manager.getEnergy() - delta.get());
|
||||||
if (actuallySpawned > 0) {
|
if (actuallySpawned > 0) {
|
||||||
Iris.info("Async Mob Spawning " + world.getName() + " used " + delta + " energy and took " + Form.duration((long) p.getMilliseconds()));
|
Iris.info("Async Mob Spawning " + world.getName() + " used " + delta + " energy and took " + Form.duration((long) p.getMilliseconds()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int trySpawn(
|
||||||
|
World world,
|
||||||
|
Set<String> invalid,
|
||||||
|
KMap<Position2, Pair<Entity[], ChunkSnapshot>> cache,
|
||||||
|
BlockPosition center,
|
||||||
|
AtomicDouble delta
|
||||||
|
) {
|
||||||
|
var pos = center.randomPoint(MAX_RADIUS, SAFE_RADIUS);
|
||||||
|
if (pos.getY() < world.getMinHeight() || pos.getY() >= world.getMaxHeight())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var chunkPos = new Position2(center.getX() >> 4, center.getZ() >> 4);
|
||||||
|
var pair = cache.computeIfAbsent(chunkPos, cPos -> {
|
||||||
|
try {
|
||||||
|
return PaperLib.getChunkAtAsync(world, cPos.getX(), cPos.getZ(), false)
|
||||||
|
.thenApply(c -> c != null ? new Pair<>(c.getEntities(), c.getChunkSnapshot(false, false, false)) : null)
|
||||||
|
.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (pair == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var spawners = spawnersAt(pair.getB(), pos, invalid);
|
||||||
|
spawners.removeIf(i -> !i.canSpawn(engine, chunkPos.getX(), chunkPos.getZ()));
|
||||||
|
|
||||||
|
if (spawners.isEmpty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
IrisPosition irisPos = new IrisPosition(pos.getX(), pos.getY(), pos.getZ());
|
||||||
|
for (var spawner : spawners) {
|
||||||
|
var spawns = spawner.getSpawns().copy();
|
||||||
|
spawns.removeIf(spawn -> !spawn.check(engine, irisPos, pair.getB()));
|
||||||
|
|
||||||
|
var entity = IRare.pick(spawns, RNG.r.nextDouble());
|
||||||
|
if (entity == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
entity.setReferenceSpawner(spawner);
|
||||||
|
entity.setReferenceMarker(spawner.getReferenceMarker());
|
||||||
|
int spawned = entity.spawn(engine, irisPos, RNG.r);
|
||||||
|
if (spawned == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
delta.addAndGet(spawned * ((entity.getEnergyMultiplier() * spawner.getEnergyMultiplier() * 1)));
|
||||||
|
spawner.spawn(engine, chunkPos.getX(), chunkPos.getZ());
|
||||||
|
if (!spawner.canSpawn(engine))
|
||||||
|
invalid.add(spawner.getLoadKey());
|
||||||
|
return spawned;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private KSet<IrisSpawner> spawnersAt(ChunkSnapshot chunk, BlockPosition pos, Set<String> invalid) {
|
private KSet<IrisSpawner> spawnersAt(ChunkSnapshot chunk, BlockPosition pos, Set<String> invalid) {
|
||||||
KSet<IrisSpawner> spawners = markerAt(chunk, pos, invalid);
|
KSet<IrisSpawner> spawners = markerAt(chunk, pos, invalid);
|
||||||
|
|
||||||
@ -296,10 +317,24 @@ public class EngineMobHandlerSVC extends IrisEngineService {
|
|||||||
private static void cancel(Thread thread) {
|
private static void cancel(Thread thread) {
|
||||||
if (thread == null || !thread.isAlive()) return;
|
if (thread == null || !thread.isAlive()) return;
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean noSpawning() {
|
private static boolean noSpawning() {
|
||||||
var world = IrisSettings.get().getWorld();
|
var world = IrisSettings.get().getWorld();
|
||||||
return !world.isMarkerEntitySpawningSystem() && !world.isAnbientEntitySpawningSystem();
|
return !world.isMarkerEntitySpawningSystem() && !world.isAnbientEntitySpawningSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean mayLoop() {
|
||||||
|
return !engine.isClosed() && running.get() && !Thread.interrupted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInterrupted(Throwable e) {
|
||||||
|
while (e != null) {
|
||||||
|
if (e instanceof InterruptedException)
|
||||||
|
return true;
|
||||||
|
e = e.getCause();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user