implement engine services

This commit is contained in:
Julian Krings
2025-03-13 22:00:55 +01:00
parent 6899761ca9
commit 647214a335
6 changed files with 274 additions and 364 deletions

View File

@@ -22,11 +22,11 @@ import com.volmit.iris.Iris;
import com.volmit.iris.core.ServerConfigurator;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.service.IrisEngineSVC;
import com.volmit.iris.core.tools.IrisPackBenchmarking;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.engine.service.EngineStatusSVC;
import com.volmit.iris.util.decree.DecreeExecutor;
import com.volmit.iris.util.decree.DecreeOrigin;
import com.volmit.iris.util.decree.annotations.Decree;
@@ -66,53 +66,20 @@ public class CommandDeveloper implements DecreeExecutor {
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
public void EngineStatus() {
List<World> IrisWorlds = new ArrayList<>();
int TotalLoadedChunks = 0;
int TotalQueuedTectonicPlates = 0;
int TotalNotQueuedTectonicPlates = 0;
int TotalTectonicPlates = 0;
var status = EngineStatusSVC.getStatus();
long lowestUnloadDuration = 0;
long highestUnloadDuration = 0;
for (World world : Bukkit.getWorlds()) {
try {
if (IrisToolbelt.access(world).getEngine() != null) {
IrisWorlds.add(world);
}
} catch (Exception e) {
// no
}
}
for (World world : IrisWorlds) {
Engine engine = IrisToolbelt.access(world).getEngine();
TotalQueuedTectonicPlates += (int) engine.getMantle().getToUnload();
TotalNotQueuedTectonicPlates += (int) engine.getMantle().getNotQueuedLoadedRegions();
TotalTectonicPlates += engine.getMantle().getLoadedRegionCount();
if (highestUnloadDuration <= (long) engine.getMantle().getTectonicDuration()) {
highestUnloadDuration = (long) engine.getMantle().getTectonicDuration();
}
if (lowestUnloadDuration >= (long) engine.getMantle().getTectonicDuration()) {
lowestUnloadDuration = (long) engine.getMantle().getTectonicDuration();
}
for (Chunk chunk : world.getLoadedChunks()) {
if (chunk.isLoaded()) {
TotalLoadedChunks++;
}
}
}
Iris.info("-------------------------");
Iris.info(C.DARK_PURPLE + "Engine Status");
Iris.info(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + TotalLoadedChunks);
Iris.info(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + IrisEngineSVC.getTectonicLimit());
Iris.info(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + TotalTectonicPlates);
Iris.info(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + TotalNotQueuedTectonicPlates);
Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + TotalQueuedTectonicPlates);
Iris.info(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(lowestUnloadDuration));
Iris.info(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(highestUnloadDuration));
Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
Iris.info("-------------------------");
sender().sendMessage("-------------------------");
sender().sendMessage(C.DARK_PURPLE + "Engine Status");
sender().sendMessage(C.DARK_PURPLE + "Total Engines: " + C.LIGHT_PURPLE + status.engineCount());
sender().sendMessage(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + status.loadedChunks());
sender().sendMessage(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + status.tectonicLimit());
sender().sendMessage(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + status.tectonicPlates());
sender().sendMessage(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + status.activeTectonicPlates());
sender().sendMessage(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + status.queuedTectonicPlates());
sender().sendMessage(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.minTectonicUnloadDuration()));
sender().sendMessage(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.maxTectonicUnloadDuration()));
sender().sendMessage(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
sender().sendMessage("-------------------------");
}
@Decree(description = "Test")

View File

@@ -1,317 +0,0 @@
package com.volmit.iris.core.service;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.mantle.TectonicPlate;
import com.volmit.iris.util.misc.getHardware;
import com.volmit.iris.util.plugin.IrisService;
import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.Looper;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.checkerframework.checker.units.qual.A;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
public class IrisEngineSVC implements IrisService {
public static IrisEngineSVC instance;
public boolean isServerShuttingDown = false;
public boolean isServerLoaded = false;
private static final AtomicInteger tectonicLimit = new AtomicInteger(30);
private ReentrantLock lastUseLock;
private KMap<World, Long> lastUse;
private List<World> IrisWorlds;
private Looper cacheTicker;
private Looper trimTicker;
private Looper unloadTicker;
private Looper updateTicker;
private PrecisionStopwatch trimAlive;
private PrecisionStopwatch unloadAlive;
public PrecisionStopwatch trimActiveAlive;
public PrecisionStopwatch unloadActiveAlive;
private AtomicInteger TotalTectonicPlates;
private AtomicInteger TotalQueuedTectonicPlates;
private AtomicInteger TotalNotQueuedTectonicPlates;
private AtomicBoolean IsUnloadAlive;
private AtomicBoolean IsTrimAlive;
ChronoLatch cl;
public List<World> corruptedIrisWorlds = new ArrayList<>();
@Override
public void onEnable() {
this.cl = new ChronoLatch(5000);
lastUse = new KMap<>();
lastUseLock = new ReentrantLock();
IrisWorlds = new ArrayList<>();
IsUnloadAlive = new AtomicBoolean(true);
IsTrimAlive = new AtomicBoolean(true);
trimActiveAlive = new PrecisionStopwatch();
unloadActiveAlive = new PrecisionStopwatch();
trimAlive = new PrecisionStopwatch();
unloadAlive = new PrecisionStopwatch();
TotalTectonicPlates = new AtomicInteger();
TotalQueuedTectonicPlates = new AtomicInteger();
TotalNotQueuedTectonicPlates = new AtomicInteger();
tectonicLimit.set(2);
long t = getHardware.getProcessMemory();
while (t > 200) {
tectonicLimit.getAndAdd(1);
t = t - 200;
}
this.setup();
this.TrimLogic();
this.UnloadLogic();
trimAlive.begin();
unloadAlive.begin();
trimActiveAlive.begin();
unloadActiveAlive.begin();
updateTicker.start();
cacheTicker.start();
//trimTicker.start();
//unloadTicker.start();
instance = this;
}
public void engineStatus() {
boolean trimAlive = trimTicker.isAlive();
boolean unloadAlive = unloadTicker.isAlive();
Iris.info("Status:");
Iris.info("- Trim: " + trimAlive);
Iris.info("- Unload: " + unloadAlive);
}
public static int getTectonicLimit() {
return tectonicLimit.get();
}
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
updateWorlds();
}
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
updateWorlds();
}
@EventHandler
public void onServerBoot(ServerLoadEvent event) {
isServerLoaded = true;
}
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
if (event.getPlugin().equals(Iris.instance)) {
isServerShuttingDown = true;
}
}
public void updateWorlds() {
for (World world : Bukkit.getWorlds()) {
try {
if (IrisToolbelt.access(world).getEngine() != null) {
IrisWorlds.add(world);
}
} catch (Exception e) {
// no
}
}
}
private void setup() {
cacheTicker = new Looper() {
@Override
protected long loop() {
long now = System.currentTimeMillis();
lastUseLock.lock();
try {
for (World key : new ArrayList<>(lastUse.keySet())) {
Long last = lastUse.get(key);
if (last == null)
continue;
if (now - last > 60000) {
lastUse.remove(key);
}
}
} finally {
lastUseLock.unlock();
}
return 1000;
}
};
updateTicker = new Looper() {
@Override
protected long loop() {
try {
TotalQueuedTectonicPlates.set(0);
TotalNotQueuedTectonicPlates.set(0);
TotalTectonicPlates.set(0);
for (World world : IrisWorlds) {
Engine engine = Objects.requireNonNull(IrisToolbelt.access(world)).getEngine();
TotalQueuedTectonicPlates.addAndGet((int) engine.getMantle().getToUnload());
TotalNotQueuedTectonicPlates.addAndGet((int) engine.getMantle().getNotQueuedLoadedRegions());
TotalTectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount());
}
if (!isServerShuttingDown && isServerLoaded) {
if (!trimTicker.isAlive()) {
Iris.info(C.RED + "TrimTicker found dead! Booting it up!");
try {
TrimLogic();
} catch (Exception e) {
Iris.error("What happened?");
e.printStackTrace();
}
}
if (!unloadTicker.isAlive()) {
Iris.info(C.RED + "UnloadTicker found dead! Booting it up!");
try {
UnloadLogic();
} catch (Exception e) {
Iris.error("What happened?");
e.printStackTrace();
}
}
}
} catch (Exception e) {
return -1;
}
return 1000;
}
};
}
public void TrimLogic() {
if (trimTicker == null || !trimTicker.isAlive()) {
trimTicker = new Looper() {
private final Supplier<Engine> supplier = createSupplier();
@Override
protected long loop() {
long start = System.currentTimeMillis();
trimAlive.reset();
try {
Engine engine = supplier.get();
if (engine != null) {
engine.getMantle().trim(tectonicLimit.get() / lastUse.size());
}
} catch (Throwable e) {
Iris.reportError(e);
Iris.info(C.RED + "EngineSVC: Failed to trim.");
e.printStackTrace();
return -1;
}
int size = lastUse.size();
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
if (time <= 0)
return 0;
return time;
}
};
trimTicker.start();
}
}
public void UnloadLogic() {
if (unloadTicker == null || !unloadTicker.isAlive()) {
unloadTicker = new Looper() {
private final Supplier<Engine> supplier = createSupplier();
@Override
protected long loop() {
long start = System.currentTimeMillis();
unloadAlive.reset();
try {
Engine engine = supplier.get();
if (engine != null) {
long unloadStart = System.currentTimeMillis();
int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / lastUse.size());
if (count > 0) {
Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
}
}
} catch (Throwable e) {
Iris.reportError(e);
Iris.info(C.RED + "EngineSVC: Failed to unload.");
e.printStackTrace();
return -1;
}
int size = lastUse.size();
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
if (time <= 0)
return 0;
return time;
}
};
unloadTicker.start();
}
}
private Supplier<Engine> createSupplier() {
AtomicInteger i = new AtomicInteger();
return () -> {
List<World> worlds = Bukkit.getWorlds();
if (i.get() >= worlds.size()) {
i.set(0);
}
try {
for (int j = 0; j < worlds.size(); j++) {
World world = worlds.get(i.getAndIncrement());
PlatformChunkGenerator generator = IrisToolbelt.access(world);
if (i.get() >= worlds.size()) {
i.set(0);
}
if (generator != null) {
Engine engine = generator.getEngine();
boolean closed = engine.getMantle().getData().isClosed();
if (engine != null && !engine.isStudio() && !closed) {
lastUseLock.lock();
lastUse.put(world, System.currentTimeMillis());
lastUseLock.unlock();
return engine;
}
}
}
} catch (Throwable e) {
Iris.info(C.RED + "EngineSVC: Failed to create supplier.");
e.printStackTrace();
Iris.reportError(e);
}
return null;
};
}
@Override
public void onDisable() {
cacheTicker.interrupt();
trimTicker.interrupt();
unloadTicker.interrupt();
lastUse.clear();
}
}

View File

@@ -43,6 +43,7 @@ import com.volmit.iris.util.format.C;
import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.io.IO;
import com.volmit.iris.util.io.JarScanner;
import com.volmit.iris.util.mantle.MantleFlag;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.math.RNG;
@@ -60,7 +61,11 @@ import org.bukkit.command.CommandSender;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -71,6 +76,8 @@ import java.util.concurrent.atomic.AtomicLong;
@EqualsAndHashCode(exclude = "context")
@ToString(exclude = "context")
public class IrisEngine implements Engine {
private static final Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> SERVICES = scanServices();
private final KMap<Class<? extends IrisEngineService>, IrisEngineService> services;
private final AtomicInteger bud;
private final AtomicInteger buds;
private final AtomicInteger generated;
@@ -111,6 +118,7 @@ public class IrisEngine implements Engine {
getEngineData();
verifySeed();
this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed());
services = new KMap<>();
bud = new AtomicInteger(0);
buds = new AtomicInteger(0);
metrics = new EngineMetrics(32);
@@ -137,6 +145,26 @@ public class IrisEngine implements Engine {
Iris.debug("Engine Initialized " + getCacheID());
}
@SuppressWarnings("unchecked")
private static Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> scanServices() {
JarScanner js = new JarScanner(Iris.instance.getJarFile(), "com.volmit.iris.engine.service");
J.attempt(js::scan);
KMap<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> map = new KMap<>();
js.getClasses()
.stream()
.filter(IrisEngineService.class::isAssignableFrom)
.map(c -> (Class<? extends IrisEngineService>) c)
.forEach(c -> {
try {
map.put(c, c.getConstructor(Engine.class));
} catch (NoSuchMethodException e) {
Iris.warn("Failed to load service " + c.getName() + " due to missing constructor");
}
});
return Collections.unmodifiableMap(map);
}
private void verifySeed() {
if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) {
target.getWorld().setRawWorldSeed(getEngineData().getSeed());
@@ -161,6 +189,8 @@ public class IrisEngine implements Engine {
execution.close();
effects.close();
mode.close();
services.values().forEach(s -> s.onDisable(true));
services.values().forEach(Iris.instance::unregisterListener);
J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace());
}
@@ -169,6 +199,24 @@ public class IrisEngine implements Engine {
try {
Iris.debug("Setup Engine " + getCacheID());
cacheId = RNG.r.nextInt();
boolean hotload = true;
if (services.isEmpty()) {
SERVICES.forEach((s, c) -> {
try {
services.put(s, c.newInstance(this));
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException e) {
Iris.error("Failed to create service " + s.getName());
e.printStackTrace();
}
});
hotload = false;
}
for (var service : services.values()) {
service.onEnable(hotload);
Iris.instance.registerListener(service);
}
worldManager = new IrisWorldManager(this);
complex = new IrisComplex(this);
execution = new IrisExecutionEnvironment(this);
@@ -418,6 +466,7 @@ public class IrisEngine implements Engine {
PregeneratorJob.shutdownInstance();
closed = true;
J.car(art);
services.values().forEach(s -> s.onDisable(false));
getWorldManager().close();
getTarget().close();
saveEngineData();

View File

@@ -0,0 +1,19 @@
package com.volmit.iris.engine.object;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.framework.Engine;
import lombok.RequiredArgsConstructor;
import org.bukkit.event.Listener;
@RequiredArgsConstructor
public abstract class IrisEngineService implements Listener {
protected final Engine engine;
public abstract void onEnable(boolean hotload);
public abstract void onDisable(boolean hotload);
public final void postShutdown(Runnable r) {
Iris.instance.postShutdown(r);
}
}

View File

@@ -0,0 +1,65 @@
package com.volmit.iris.engine.service;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisEngineService;
import com.volmit.iris.util.collection.KList;
public class EngineStatusSVC extends IrisEngineService {
private static final KList<EngineStatusSVC> INSTANCES = new KList<>();
public EngineStatusSVC(Engine engine) {
super(engine);
}
@Override
public void onEnable(boolean hotload) {
if (hotload) return;
synchronized (INSTANCES) {
INSTANCES.add(this);
}
}
@Override
public void onDisable(boolean hotload) {
if (hotload) return;
synchronized (INSTANCES) {
INSTANCES.remove(this);
}
}
public static int getEngineCount() {
return INSTANCES.size();
}
public static Status getStatus() {
synchronized (INSTANCES) {
long loadedChunks = 0;
long tectonicPlates = 0;
long activeTectonicPlates = 0;
long queuedTectonicPlates = 0;
long minTectonicUnloadDuration = Long.MAX_VALUE;
long maxTectonicUnloadDuration = Long.MIN_VALUE;
for (var service : INSTANCES) {
var world = service.engine.getWorld();
if (world.hasRealWorld())
loadedChunks += world.realWorld().getLoadedChunks().length;
tectonicPlates += service.engine.getMantle().getLoadedRegionCount();
activeTectonicPlates += service.engine.getMantle().getNotQueuedLoadedRegions();
queuedTectonicPlates += service.engine.getMantle().getToUnload();
minTectonicUnloadDuration = Math.min(minTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration());
maxTectonicUnloadDuration = Math.max(maxTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration());
}
return new Status(INSTANCES.size(), loadedChunks, MantleCleanerSVC.getTectonicLimit(), tectonicPlates, activeTectonicPlates, queuedTectonicPlates, minTectonicUnloadDuration, maxTectonicUnloadDuration);
}
}
public record Status(int engineCount, long loadedChunks, int tectonicLimit,
long tectonicPlates, long activeTectonicPlates,
long queuedTectonicPlates,
long minTectonicUnloadDuration,
long maxTectonicUnloadDuration) {
}
}

View File

@@ -0,0 +1,127 @@
package com.volmit.iris.engine.service;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisEngineService;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.misc.getHardware;
import com.volmit.iris.util.scheduling.Looper;
import lombok.SneakyThrows;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.LongSupplier;
public class MantleCleanerSVC extends IrisEngineService {
private static final AtomicInteger tectonicLimit = new AtomicInteger(30);
static {
// todo: Redo this
tectonicLimit.set(2);
long t = getHardware.getProcessMemory();
while (t > 200) {
tectonicLimit.incrementAndGet();
t = t - 200;
}
}
private Ticker trimmer;
private Ticker unloader;
public MantleCleanerSVC(Engine engine) {
super(engine);
}
public static int getTectonicLimit() {
return tectonicLimit.get();
}
private static Ticker createTrimmer(Engine engine) {
return new Ticker(() -> {
if (engine.isClosed()) return -1;
long start = M.ms();
try {
engine.getMantle().trim(tectonicLimit.get() / getEngineCount());
} catch (Throwable e) {
Iris.debug(C.RED + "Mantle: Failed to trim.");
Iris.reportError(e);
e.printStackTrace();
}
if (engine.isClosed()) return -1;
int size = getEngineCount();
return Math.max(1000 / size - (M.ms() - start), 0);
}, "Iris Mantle Trimmer - " + engine.getWorld().name());
}
private static Ticker createUnloader(Engine engine) {
return new Ticker(() -> {
if (engine.isClosed()) return -1;
long start = M.ms();
try {
engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / getEngineCount());
} catch (Throwable e) {
Iris.debug(C.RED + "Mantle: Failed to unload.");
Iris.reportError(e);
e.printStackTrace();
}
if (engine.isClosed()) return -1;
int size = getEngineCount();
return Math.max(1000 / size - (M.ms() - start), 0);
}, "Iris Mantle Unloader - " + engine.getWorld().name());
}
private static int getEngineCount() {
return Math.max(EngineStatusSVC.getEngineCount(), 1);
}
@Override
public void onEnable(boolean hotload) {
if (engine.isStudio() && !IrisSettings.get().getPerformance().trimMantleInStudio)
return;
if (trimmer == null || !trimmer.isAlive())
trimmer = createTrimmer(engine);
if (unloader == null || !unloader.isAlive())
unloader = createUnloader(engine);
}
@Override
public void onDisable(boolean hotload) {
if (hotload) return;
if (trimmer != null) trimmer.await();
if (unloader != null) unloader.await();
}
private static class Ticker extends Looper {
private final LongSupplier supplier;
private Ticker(LongSupplier supplier, String name) {
this.supplier = supplier;
setPriority(Thread.MIN_PRIORITY);
setName(name);
start();
}
@Override
protected long loop() {
try {
return supplier.getAsLong();
} catch (Throwable e) {
Iris.error("Exception in Looper " + getName());
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
Iris.error(sw.toString());
return 3000;
}
}
@SneakyThrows
public void await() {
join();
}
}
}