mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-28 13:00:43 +00:00
d
This commit is contained in:
@@ -55,6 +55,7 @@ import art.arcane.iris.engine.EnginePanic;
|
||||
import art.arcane.iris.engine.framework.BlockEditAccess;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.PreservationRegistry;
|
||||
import art.arcane.iris.engine.framework.TreeBlockMaterial;
|
||||
import art.arcane.iris.engine.object.IrisCompat;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
@@ -634,11 +635,8 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
IrisServices.register(EngineComponentCleanup.class, (EngineComponentCleanup) BukkitPlatform::unregisterListener);
|
||||
IrisServices.register(EngineEffectsProvider.class, (EngineEffectsProvider) IrisEngineEffects::new);
|
||||
IrisServices.register(EnginePlatformHooks.class, new BukkitEnginePlatformHooks());
|
||||
IrisServices.register(EngineWorldManagerProvider.class, (EngineWorldManagerProvider) (Engine engine) -> {
|
||||
IrisWorldManager manager = new IrisWorldManager(engine);
|
||||
manager.startManager();
|
||||
return manager;
|
||||
});
|
||||
IrisServices.register(EngineWorldManagerProvider.class,
|
||||
(EngineWorldManagerProvider) IrisWorldManager::new);
|
||||
IrisServices.register(art.arcane.iris.core.runtime.WorldDeletionQueue.class, (art.arcane.iris.core.runtime.WorldDeletionQueue) Iris::queueWorldDeletionOnStartup);
|
||||
settingsFile = getDataFile("settings.json");
|
||||
configHotloadEngine = new ConfigHotloadEngine(
|
||||
@@ -673,6 +671,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
}
|
||||
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
|
||||
IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName());
|
||||
IrisToolbelt.retainMantleDataForSlice(TreeBlockMaterial.class.getCanonicalName());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package art.arcane.iris.api.tree;
|
||||
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
public interface IrisTreeFellerService {
|
||||
boolean tryFell(BlockBreakEvent event, TreeFellerOptions options);
|
||||
|
||||
boolean isManagedBreak(BlockBreakEvent event);
|
||||
|
||||
boolean isTreeBlock(Block block);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package art.arcane.iris.api.tree;
|
||||
|
||||
public enum TreeFellerAccess {
|
||||
STANDALONE,
|
||||
INTEGRATION_OVERRIDE
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package art.arcane.iris.api.tree;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record TreeFellerOptions(
|
||||
TreeFellerAccess access,
|
||||
int durabilityPreservationChance,
|
||||
TreeFellerRunHooks runHooks
|
||||
) {
|
||||
public TreeFellerOptions {
|
||||
Objects.requireNonNull(access, "access");
|
||||
Objects.requireNonNull(runHooks, "runHooks");
|
||||
if (durabilityPreservationChance < 0 || durabilityPreservationChance > 100) {
|
||||
throw new IllegalArgumentException("durabilityPreservationChance must be between 0 and 100");
|
||||
}
|
||||
}
|
||||
|
||||
public static TreeFellerOptions standalone() {
|
||||
return new TreeFellerOptions(TreeFellerAccess.STANDALONE, 0, TreeFellerRunHooks.NONE);
|
||||
}
|
||||
|
||||
public static TreeFellerOptions integrationOverride(
|
||||
int durabilityPreservationChance,
|
||||
TreeFellerRunHooks runHooks
|
||||
) {
|
||||
return new TreeFellerOptions(
|
||||
TreeFellerAccess.INTEGRATION_OVERRIDE,
|
||||
durabilityPreservationChance,
|
||||
runHooks
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package art.arcane.iris.api.tree;
|
||||
|
||||
public interface TreeFellerRunHooks {
|
||||
TreeFellerRunHooks NONE = new TreeFellerRunHooks() {
|
||||
@Override
|
||||
public void onActivationAccepted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reserveLogCost() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitLogCost() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refundLogCost() {
|
||||
}
|
||||
};
|
||||
|
||||
void onActivationAccepted();
|
||||
|
||||
boolean reserveLogCost();
|
||||
|
||||
void commitLogCost();
|
||||
|
||||
void refundLogCost();
|
||||
}
|
||||
+4
-1
@@ -80,7 +80,10 @@ public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
|
||||
|
||||
@Override
|
||||
public void shutdownPregenerator(Engine engine) {
|
||||
PregeneratorJob.shutdownInstance();
|
||||
IrisWorld world = engine.getWorld();
|
||||
if (world != null) {
|
||||
PregeneratorJob.shutdownInstanceForWorld(world.identity());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+610
-365
File diff suppressed because it is too large
Load Diff
+91
@@ -0,0 +1,91 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.framework.MeteredCache;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedDoubleStream2D;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedStream2D;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedStream3D;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class IrisEngineStatus {
|
||||
private IrisEngineStatus() {
|
||||
}
|
||||
|
||||
static void send(VolmitSender sender, Snapshot snapshot) {
|
||||
CacheSummary caches = summarizeCaches();
|
||||
MaintenanceMetrics metrics = snapshot.metrics();
|
||||
|
||||
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||
sender.sendMessage(C.DARK_PURPLE + "Status:");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Service: " + C.LIGHT_PURPLE + (snapshot.serviceRunning() ? "Running" : "Stopped"));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Metrics: " + C.LIGHT_PURPLE + (snapshot.metricsRunning() ? "Running" : "Stopped"));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Maintenance Period: " + C.LIGHT_PURPLE + Form.duration(snapshot.maintenancePeriodMillis()));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Worker Parallelism: " + C.LIGHT_PURPLE + snapshot.workerParallelism());
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Active World Tasks: " + C.LIGHT_PURPLE + metrics.activeTasks());
|
||||
sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Configured Retention: " + C.LIGHT_PURPLE + Form.duration(snapshot.retentionMillis()));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Heap Usage: " + C.LIGHT_PURPLE + Form.pc(snapshot.heapUsage()));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Resident: " + C.LIGHT_PURPLE + metrics.residentTectonicPlates());
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Queued: " + C.LIGHT_PURPLE + metrics.queuedTectonicPlates());
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Average Idle Duration: " + C.LIGHT_PURPLE + Form.duration(metrics.averageIdleDuration(), 2));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(metrics.maxIdleDuration(), 2));
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(metrics.minIdleDuration(), 2));
|
||||
sender.sendMessage(C.DARK_PURPLE + "Caches:");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Resource: " + C.LIGHT_PURPLE + caches.sizes()[0] + " (" + caches.counts()[0] + ")");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- 2D Stream: " + C.LIGHT_PURPLE + caches.sizes()[1] + " (" + caches.counts()[1] + ")");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- 3D Stream: " + C.LIGHT_PURPLE + caches.sizes()[2] + " (" + caches.counts()[2] + ")");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Other: " + C.LIGHT_PURPLE + caches.sizes()[3] + " (" + caches.counts()[3] + ")");
|
||||
sender.sendMessage(C.DARK_PURPLE + "Other:");
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + metrics.worlds());
|
||||
sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + metrics.loadedChunks());
|
||||
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||
}
|
||||
|
||||
private static CacheSummary summarizeCaches() {
|
||||
long[] sizes = new long[4];
|
||||
long[] counts = new long[4];
|
||||
PreservationSVC preservation = IrisServices.get(PreservationSVC.class);
|
||||
List<MeteredCache> caches = preservation == null ? List.of() : preservation.getCaches();
|
||||
|
||||
for (MeteredCache cache : caches) {
|
||||
int type = switch (cache) {
|
||||
case ResourceLoader<?> ignored -> 0;
|
||||
case CachedStream2D<?> ignored -> 1;
|
||||
case CachedDoubleStream2D ignored -> 1;
|
||||
case CachedStream3D<?> ignored -> 2;
|
||||
default -> 3;
|
||||
};
|
||||
sizes[type] += cache.getSize();
|
||||
counts[type]++;
|
||||
}
|
||||
return new CacheSummary(sizes, counts);
|
||||
}
|
||||
|
||||
record Snapshot(boolean serviceRunning,
|
||||
boolean metricsRunning,
|
||||
long maintenancePeriodMillis,
|
||||
int workerParallelism,
|
||||
long retentionMillis,
|
||||
double heapUsage,
|
||||
MaintenanceMetrics metrics) {
|
||||
}
|
||||
|
||||
record MaintenanceMetrics(long residentTectonicPlates,
|
||||
long queuedTectonicPlates,
|
||||
long loadedChunks,
|
||||
int worlds,
|
||||
int activeTasks,
|
||||
double averageIdleDuration,
|
||||
double maxIdleDuration,
|
||||
double minIdleDuration) {
|
||||
static final MaintenanceMetrics EMPTY = new MaintenanceMetrics(0L, 0L, 0L, 0, 0, 0D, 0D, 0D);
|
||||
}
|
||||
|
||||
private record CacheSummary(long[] sizes, long[] counts) {
|
||||
}
|
||||
}
|
||||
+251
-76
@@ -1,12 +1,14 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.engine.framework.EngineTelemetrySnapshot;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import art.arcane.volmlib.integration.IntegrationHandshakeRequest;
|
||||
import art.arcane.volmlib.integration.IntegrationHandshakeResponse;
|
||||
import art.arcane.volmlib.integration.IntegrationHeartbeat;
|
||||
import art.arcane.volmlib.integration.IntegrationMetricDescriptor;
|
||||
import art.arcane.volmlib.integration.IntegrationMetricGroup;
|
||||
import art.arcane.volmlib.integration.IntegrationMetricSample;
|
||||
import art.arcane.volmlib.integration.IntegrationMetricSchema;
|
||||
import art.arcane.volmlib.integration.IntegrationProtocolNegotiator;
|
||||
@@ -15,25 +17,59 @@ import art.arcane.volmlib.integration.IntegrationServiceContract;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IrisIntegrationService implements IrisService, IntegrationServiceContract {
|
||||
private static final IntegrationProtocolVersion CURRENT_PROTOCOL = new IntegrationProtocolVersion(1, 2);
|
||||
private static final Set<IntegrationProtocolVersion> SUPPORTED_PROTOCOLS = Set.of(
|
||||
new IntegrationProtocolVersion(1, 0),
|
||||
new IntegrationProtocolVersion(1, 1)
|
||||
new IntegrationProtocolVersion(1, 1),
|
||||
CURRENT_PROTOCOL
|
||||
);
|
||||
|
||||
private static final Set<String> CAPABILITIES = Set.of(
|
||||
"handshake",
|
||||
"heartbeat",
|
||||
"metrics",
|
||||
"iris-engine-metrics"
|
||||
"metric-groups",
|
||||
"iris-engine-metrics",
|
||||
"iris-world-metrics"
|
||||
);
|
||||
private static final Map<String, String> TIMING_KEYS = Map.ofEntries(
|
||||
Map.entry("total", IntegrationMetricSchema.IRIS_GENERATION_TOTAL_MS),
|
||||
Map.entry("updates", IntegrationMetricSchema.IRIS_GENERATION_UPDATES_MS),
|
||||
Map.entry("terrain", IntegrationMetricSchema.IRIS_GENERATION_TERRAIN_MS),
|
||||
Map.entry("biome", IntegrationMetricSchema.IRIS_GENERATION_BIOME_MS),
|
||||
Map.entry("post", IntegrationMetricSchema.IRIS_GENERATION_POST_MS),
|
||||
Map.entry("perfection", IntegrationMetricSchema.IRIS_GENERATION_PERFECTION_MS),
|
||||
Map.entry("decoration", IntegrationMetricSchema.IRIS_GENERATION_DECORATION_MS),
|
||||
Map.entry("cave", IntegrationMetricSchema.IRIS_GENERATION_CAVE_MS),
|
||||
Map.entry("deposit", IntegrationMetricSchema.IRIS_GENERATION_DEPOSIT_MS),
|
||||
Map.entry("carve.resolve", IntegrationMetricSchema.IRIS_GENERATION_CARVE_RESOLVE_MS),
|
||||
Map.entry("carve.apply", IntegrationMetricSchema.IRIS_GENERATION_CARVE_APPLY_MS),
|
||||
Map.entry("context.prefill", IntegrationMetricSchema.IRIS_GENERATION_CONTEXT_PREFILL_MS),
|
||||
Map.entry("pregen.wait.permit", IntegrationMetricSchema.IRIS_PREGEN_WAIT_PERMIT_MS),
|
||||
Map.entry("pregen.wait.adaptive", IntegrationMetricSchema.IRIS_PREGEN_WAIT_ADAPTIVE_MS)
|
||||
);
|
||||
|
||||
private volatile IntegrationProtocolVersion negotiatedProtocol = new IntegrationProtocolVersion(1, 1);
|
||||
private final Supplier<IrisTelemetrySnapshot> telemetrySupplier;
|
||||
|
||||
public IrisIntegrationService() {
|
||||
this(IrisIntegrationService::currentTelemetry);
|
||||
}
|
||||
|
||||
IrisIntegrationService(Supplier<IrisTelemetrySnapshot> telemetrySupplier) {
|
||||
this.telemetrySupplier = telemetrySupplier == null
|
||||
? () -> IrisTelemetrySnapshot.EMPTY
|
||||
: telemetrySupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -68,9 +104,11 @@ public class IrisIntegrationService implements IrisService, IntegrationServiceCo
|
||||
|
||||
@Override
|
||||
public Set<IntegrationMetricDescriptor> metricDescriptors() {
|
||||
return IntegrationMetricSchema.descriptors().stream()
|
||||
.filter(descriptor -> descriptor.key().startsWith("iris."))
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
Set<IntegrationMetricDescriptor> descriptors = new LinkedHashSet<>();
|
||||
for (String key : IntegrationMetricSchema.irisKeys()) {
|
||||
descriptors.add(IntegrationMetricSchema.descriptor(key));
|
||||
}
|
||||
return Set.copyOf(descriptors);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,12 +144,11 @@ public class IrisIntegrationService implements IrisService, IntegrationServiceCo
|
||||
);
|
||||
}
|
||||
|
||||
negotiatedProtocol = negotiated.get();
|
||||
return new IntegrationHandshakeResponse(
|
||||
pluginId(),
|
||||
pluginVersion(),
|
||||
true,
|
||||
negotiatedProtocol,
|
||||
negotiated.get(),
|
||||
SUPPORTED_PROTOCOLS,
|
||||
CAPABILITIES,
|
||||
"ok",
|
||||
@@ -121,90 +158,228 @@ public class IrisIntegrationService implements IrisService, IntegrationServiceCo
|
||||
|
||||
@Override
|
||||
public IntegrationHeartbeat heartbeat() {
|
||||
long now = System.currentTimeMillis();
|
||||
return new IntegrationHeartbeat(negotiatedProtocol, true, now, "ok");
|
||||
return new IntegrationHeartbeat(CURRENT_PROTOCOL, true, System.currentTimeMillis(), "ok");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, IntegrationMetricSample> sampleMetrics(Set<String> metricKeys) {
|
||||
Set<String> requested = metricKeys == null || metricKeys.isEmpty()
|
||||
? IntegrationMetricSchema.irisKeys()
|
||||
: metricKeys;
|
||||
long now = System.currentTimeMillis();
|
||||
Map<String, IntegrationMetricSample> out = new HashMap<>();
|
||||
: Set.copyOf(metricKeys);
|
||||
IrisTelemetrySnapshot snapshot = safeSnapshot();
|
||||
long sampledAtMs = snapshot.sampledAtMs() > 0L
|
||||
? snapshot.sampledAtMs()
|
||||
: System.currentTimeMillis();
|
||||
if (snapshot.sampledAtMs() <= 0L) {
|
||||
return unavailableMetrics(requested, "telemetry-not-ready", sampledAtMs);
|
||||
}
|
||||
|
||||
Map<String, IntegrationMetricSample> available = globalSamples(snapshot);
|
||||
Map<String, IntegrationMetricSample> selected = new LinkedHashMap<>(requested.size());
|
||||
for (String key : requested) {
|
||||
switch (key) {
|
||||
case IntegrationMetricSchema.IRIS_CHUNK_STREAM_MS -> out.put(key, sampleChunkStreamMetric(now));
|
||||
case IntegrationMetricSchema.IRIS_PREGEN_QUEUE -> out.put(key, samplePregenQueueMetric(now));
|
||||
case IntegrationMetricSchema.IRIS_BIOME_CACHE_HIT_RATE -> out.put(key, sampleBiomeCacheHitRateMetric(now));
|
||||
default -> out.put(key, IntegrationMetricSample.unavailable(
|
||||
IntegrationMetricSchema.descriptor(key),
|
||||
"unsupported-key",
|
||||
now
|
||||
));
|
||||
IntegrationMetricSample sample = available.get(key);
|
||||
selected.put(key, sample == null
|
||||
? unavailable(key, "unsupported-key", sampledAtMs)
|
||||
: sample);
|
||||
}
|
||||
return Map.copyOf(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IntegrationMetricGroup> metricGroups() {
|
||||
IrisTelemetrySnapshot snapshot = safeSnapshot();
|
||||
if (snapshot.sampledAtMs() <= 0L || snapshot.worlds().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<EngineTelemetrySnapshot> worlds = new ArrayList<>(snapshot.worlds());
|
||||
worlds.sort(Comparator.comparing(EngineTelemetrySnapshot::worldIdentity));
|
||||
List<IntegrationMetricGroup> groups = new ArrayList<>(worlds.size());
|
||||
for (EngineTelemetrySnapshot world : worlds) {
|
||||
groups.add(new IntegrationMetricGroup(
|
||||
"world",
|
||||
world.worldIdentity(),
|
||||
world.worldName(),
|
||||
Map.of(
|
||||
"plugin", "iris",
|
||||
"dimension", world.dimensionKey()
|
||||
),
|
||||
worldSamples(world, snapshot.pregenerator())
|
||||
));
|
||||
}
|
||||
return List.copyOf(groups);
|
||||
}
|
||||
|
||||
private Map<String, IntegrationMetricSample> globalSamples(IrisTelemetrySnapshot snapshot) {
|
||||
long sampledAtMs = snapshot.sampledAtMs();
|
||||
EngineTelemetrySnapshot.Aggregate aggregate = snapshot.aggregate();
|
||||
Map<String, IntegrationMetricSample> samples = new LinkedHashMap<>();
|
||||
put(samples, IntegrationMetricSchema.IRIS_WORLD_COUNT, aggregate.worldCount(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_ACTIVE, aggregate.active(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_CLOSING, aggregate.closing() + snapshot.closingEngines(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_FAILED, aggregate.failed(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_STUDIO, aggregate.studio(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_PENDING_REGISTRATIONS, snapshot.pendingRegistrations(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_LOADED_CHUNKS, aggregate.loadedChunks(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_LOADED_ENTITIES, aggregate.loadedEntities(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENTITY_SATURATION, aggregate.entitySaturationMax(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_GENERATED_SESSION, aggregate.generatedSession(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_GENERATED_TOTAL, aggregate.generatedTotal(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_PER_SECOND, aggregate.chunksPerSecond(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_BLOCK_UPDATES_PER_SECOND, aggregate.blockUpdatesPerSecond(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_PARALLELISM, aggregate.parallelism(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_GENERATION_ACTIVE_LEASES, aggregate.activeGenerationLeases(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_HOTLOADS_TOTAL, aggregate.hotloadsTotal(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MAINTENANCE_ACTIVE_TASKS, snapshot.maintenanceActiveTasks(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MAINTENANCE_WORKERS, snapshot.maintenanceWorkers(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_RESIDENT_PLATES, aggregate.mantleResidentPlates(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_QUEUED_PLATES, aggregate.mantleQueuedPlates(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_IDLE_AVERAGE_MS, aggregate.mantleIdleAverageMs(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_IDLE_MAX_MS, aggregate.mantleIdleMaxMs(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_IDLE_MIN_MS, aggregate.mantleIdleMinMs(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_HEAP_USAGE, snapshot.heapUsage(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_RECLAIM_URGENCY, snapshot.reclaimUrgency(), sampledAtMs);
|
||||
putCacheSamples(samples, snapshot.caches(), sampledAtMs);
|
||||
putPregenSamples(samples, snapshot.pregenerator(), sampledAtMs);
|
||||
putTimingSamples(samples, aggregate.generationTimingMaximaMs(), sampledAtMs);
|
||||
return Map.copyOf(samples);
|
||||
}
|
||||
|
||||
private Map<String, IntegrationMetricSample> worldSamples(
|
||||
EngineTelemetrySnapshot world,
|
||||
IrisTelemetrySnapshot.PregenSnapshot pregenerator
|
||||
) {
|
||||
long sampledAtMs = world.sampledAtMs();
|
||||
Map<String, IntegrationMetricSample> samples = new LinkedHashMap<>();
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_ACTIVE, world.active() ? 1 : 0, sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_CLOSING, world.closing() ? 1 : 0, sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_FAILED, world.failed() ? 1 : 0, sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_STUDIO, world.studio() ? 1 : 0, sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_LOADED_CHUNKS, world.loadedChunks(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_LOADED_ENTITIES, world.loadedEntities(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENTITY_SATURATION, world.entitySaturation(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_GENERATED_SESSION, world.generatedSession(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_GENERATED_TOTAL, world.generatedTotal(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_CHUNKS_PER_SECOND, world.chunksPerSecond(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_BLOCK_UPDATES_PER_SECOND, world.blockUpdatesPerSecond(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_ENGINE_PARALLELISM, world.parallelism(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_GENERATION_ACTIVE_LEASES, world.activeGenerationLeases(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_HOTLOADS_TOTAL, world.hotloadsTotal(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_RESIDENT_PLATES, world.mantleResidentPlates(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_QUEUED_PLATES, world.mantleQueuedPlates(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_MANTLE_IDLE_AVERAGE_MS, world.mantleIdleMs(), sampledAtMs);
|
||||
boolean targetsWorld = pregenerator.active()
|
||||
&& world.worldIdentity().equals(pregenerator.worldIdentity());
|
||||
putPregenSamples(samples, targetsWorld ? pregenerator : IrisTelemetrySnapshot.PregenSnapshot.INACTIVE, sampledAtMs);
|
||||
putTimingSamples(samples, world.generationTimingsMs(), sampledAtMs);
|
||||
samples.keySet().retainAll(IntegrationMetricSchema.irisWorldKeys());
|
||||
return Map.copyOf(samples);
|
||||
}
|
||||
|
||||
private void putCacheSamples(
|
||||
Map<String, IntegrationMetricSample> samples,
|
||||
IrisTelemetrySnapshot.CacheSnapshot caches,
|
||||
long sampledAtMs
|
||||
) {
|
||||
putCacheBucket(samples, caches.total(), IntegrationMetricSchema.IRIS_CACHE_COUNT, IntegrationMetricSchema.IRIS_CACHE_ENTRIES, IntegrationMetricSchema.IRIS_CACHE_CAPACITY, IntegrationMetricSchema.IRIS_CACHE_USAGE, sampledAtMs);
|
||||
putCacheBucket(samples, caches.resource(), IntegrationMetricSchema.IRIS_CACHE_RESOURCE_COUNT, IntegrationMetricSchema.IRIS_CACHE_RESOURCE_ENTRIES, IntegrationMetricSchema.IRIS_CACHE_RESOURCE_CAPACITY, IntegrationMetricSchema.IRIS_CACHE_RESOURCE_USAGE, sampledAtMs);
|
||||
putCacheBucket(samples, caches.stream2d(), IntegrationMetricSchema.IRIS_CACHE_STREAM_2D_COUNT, IntegrationMetricSchema.IRIS_CACHE_STREAM_2D_ENTRIES, IntegrationMetricSchema.IRIS_CACHE_STREAM_2D_CAPACITY, IntegrationMetricSchema.IRIS_CACHE_STREAM_2D_USAGE, sampledAtMs);
|
||||
putCacheBucket(samples, caches.stream3d(), IntegrationMetricSchema.IRIS_CACHE_STREAM_3D_COUNT, IntegrationMetricSchema.IRIS_CACHE_STREAM_3D_ENTRIES, IntegrationMetricSchema.IRIS_CACHE_STREAM_3D_CAPACITY, IntegrationMetricSchema.IRIS_CACHE_STREAM_3D_USAGE, sampledAtMs);
|
||||
putCacheBucket(samples, caches.other(), IntegrationMetricSchema.IRIS_CACHE_OTHER_COUNT, IntegrationMetricSchema.IRIS_CACHE_OTHER_ENTRIES, IntegrationMetricSchema.IRIS_CACHE_OTHER_CAPACITY, IntegrationMetricSchema.IRIS_CACHE_OTHER_USAGE, sampledAtMs);
|
||||
}
|
||||
|
||||
private void putCacheBucket(
|
||||
Map<String, IntegrationMetricSample> samples,
|
||||
IrisTelemetrySnapshot.CacheBucket bucket,
|
||||
String countKey,
|
||||
String entriesKey,
|
||||
String capacityKey,
|
||||
String usageKey,
|
||||
long sampledAtMs
|
||||
) {
|
||||
put(samples, countKey, bucket.count(), sampledAtMs);
|
||||
put(samples, entriesKey, bucket.entries(), sampledAtMs);
|
||||
put(samples, capacityKey, bucket.capacity(), sampledAtMs);
|
||||
put(samples, usageKey, bucket.usage(), sampledAtMs);
|
||||
}
|
||||
|
||||
private void putPregenSamples(
|
||||
Map<String, IntegrationMetricSample> samples,
|
||||
IrisTelemetrySnapshot.PregenSnapshot pregenerator,
|
||||
long sampledAtMs
|
||||
) {
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_ACTIVE, pregenerator.active() ? 1 : 0, sampledAtMs);
|
||||
if (!pregenerator.active()) {
|
||||
for (String key : Set.of(
|
||||
IntegrationMetricSchema.IRIS_PREGEN_PAUSED,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_PROGRESS,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_GENERATED,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_TOTAL,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_QUEUE,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_THROUGHPUT,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_ETA_MS,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_ELAPSED_MS,
|
||||
IntegrationMetricSchema.IRIS_PREGEN_FAILED)) {
|
||||
samples.put(key, unavailable(key, "pregen-inactive", sampledAtMs));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return out;
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_PAUSED, pregenerator.paused() ? 1 : 0, sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_PROGRESS, pregenerator.progressPercent(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_GENERATED, pregenerator.generated(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_TOTAL, pregenerator.total(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_QUEUE, pregenerator.remaining(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_THROUGHPUT, pregenerator.chunksPerSecond(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_ETA_MS, pregenerator.etaMs(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_ELAPSED_MS, pregenerator.elapsedMs(), sampledAtMs);
|
||||
put(samples, IntegrationMetricSchema.IRIS_PREGEN_FAILED, pregenerator.failed(), sampledAtMs);
|
||||
}
|
||||
|
||||
private IntegrationMetricSample sampleChunkStreamMetric(long now) {
|
||||
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_CHUNK_STREAM_MS);
|
||||
|
||||
double chunksPerSecond = PregeneratorJob.chunksPerSecond();
|
||||
|
||||
if (chunksPerSecond > 0D) {
|
||||
return IntegrationMetricSample.available(descriptor, 1000D / chunksPerSecond, now);
|
||||
private void putTimingSamples(
|
||||
Map<String, IntegrationMetricSample> samples,
|
||||
Map<String, Double> timings,
|
||||
long sampledAtMs
|
||||
) {
|
||||
for (Map.Entry<String, String> entry : TIMING_KEYS.entrySet()) {
|
||||
Double value = timings.get(entry.getKey());
|
||||
samples.put(entry.getValue(), value == null
|
||||
? unavailable(entry.getValue(), "timing-not-available", sampledAtMs)
|
||||
: available(entry.getValue(), value, sampledAtMs));
|
||||
}
|
||||
|
||||
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||
if (engineService != null) {
|
||||
double idle = engineService.getAverageIdleDuration();
|
||||
if (idle > 0D && Double.isFinite(idle)) {
|
||||
return IntegrationMetricSample.available(descriptor, idle, now);
|
||||
}
|
||||
}
|
||||
|
||||
return IntegrationMetricSample.available(descriptor, 0D, now);
|
||||
}
|
||||
|
||||
private IntegrationMetricSample samplePregenQueueMetric(long now) {
|
||||
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_PREGEN_QUEUE);
|
||||
long totalQueue = 0L;
|
||||
boolean hasAnySource = false;
|
||||
|
||||
long pregenRemaining = PregeneratorJob.chunksRemaining();
|
||||
if (pregenRemaining >= 0L) {
|
||||
totalQueue += pregenRemaining;
|
||||
hasAnySource = true;
|
||||
}
|
||||
|
||||
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||
if (engineService != null) {
|
||||
totalQueue += Math.max(0, engineService.getQueuedTectonicPlateCount());
|
||||
hasAnySource = true;
|
||||
}
|
||||
|
||||
if (!hasAnySource) {
|
||||
return IntegrationMetricSample.unavailable(descriptor, "queue-not-available", now);
|
||||
}
|
||||
|
||||
return IntegrationMetricSample.available(descriptor, totalQueue, now);
|
||||
private void put(Map<String, IntegrationMetricSample> samples, String key, double value, long sampledAtMs) {
|
||||
samples.put(key, available(key, value, sampledAtMs));
|
||||
}
|
||||
|
||||
private IntegrationMetricSample sampleBiomeCacheHitRateMetric(long now) {
|
||||
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_BIOME_CACHE_HIT_RATE);
|
||||
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||
if (engineService == null) {
|
||||
return IntegrationMetricSample.unavailable(descriptor, "engine-service-unavailable", now);
|
||||
}
|
||||
private IntegrationMetricSample available(String key, double value, long sampledAtMs) {
|
||||
return IntegrationMetricSample.available(IntegrationMetricSchema.descriptor(key), value, sampledAtMs);
|
||||
}
|
||||
|
||||
double ratio = engineService.getBiomeCacheUsageRatio();
|
||||
if (!Double.isFinite(ratio)) {
|
||||
return IntegrationMetricSample.unavailable(descriptor, "biome-cache-ratio-invalid", now);
|
||||
}
|
||||
private IntegrationMetricSample unavailable(String key, String reason, long sampledAtMs) {
|
||||
return IntegrationMetricSample.unavailable(IntegrationMetricSchema.descriptor(key), reason, sampledAtMs);
|
||||
}
|
||||
|
||||
return IntegrationMetricSample.available(descriptor, Math.max(0D, Math.min(1D, ratio)), now);
|
||||
private Map<String, IntegrationMetricSample> unavailableMetrics(
|
||||
Set<String> keys,
|
||||
String reason,
|
||||
long sampledAtMs
|
||||
) {
|
||||
Map<String, IntegrationMetricSample> samples = new LinkedHashMap<>(keys.size());
|
||||
for (String key : keys) {
|
||||
samples.put(key, unavailable(key, reason, sampledAtMs));
|
||||
}
|
||||
return Map.copyOf(samples);
|
||||
}
|
||||
|
||||
private IrisTelemetrySnapshot safeSnapshot() {
|
||||
IrisTelemetrySnapshot snapshot = telemetrySupplier.get();
|
||||
return snapshot == null ? IrisTelemetrySnapshot.EMPTY : snapshot;
|
||||
}
|
||||
|
||||
private static IrisTelemetrySnapshot currentTelemetry() {
|
||||
IrisEngineSVC service = IrisServices.get(IrisEngineSVC.class);
|
||||
return service == null ? IrisTelemetrySnapshot.EMPTY : service.telemetrySnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.engine.framework.EngineTelemetrySnapshot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
record IrisTelemetrySnapshot(
|
||||
long sampledAtMs,
|
||||
List<EngineTelemetrySnapshot> worlds,
|
||||
EngineTelemetrySnapshot.Aggregate aggregate,
|
||||
int maintenanceActiveTasks,
|
||||
int maintenanceWorkers,
|
||||
int closingEngines,
|
||||
int pendingRegistrations,
|
||||
double heapUsage,
|
||||
double reclaimUrgency,
|
||||
CacheSnapshot caches,
|
||||
PregenSnapshot pregenerator
|
||||
) {
|
||||
static final IrisTelemetrySnapshot EMPTY = new IrisTelemetrySnapshot(
|
||||
0L,
|
||||
List.of(),
|
||||
EngineTelemetrySnapshot.aggregate(List.of()),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0D,
|
||||
0D,
|
||||
CacheSnapshot.EMPTY,
|
||||
PregenSnapshot.INACTIVE
|
||||
);
|
||||
|
||||
IrisTelemetrySnapshot {
|
||||
worlds = worlds == null ? List.of() : List.copyOf(worlds);
|
||||
aggregate = aggregate == null ? EngineTelemetrySnapshot.aggregate(worlds) : aggregate;
|
||||
maintenanceActiveTasks = Math.max(0, maintenanceActiveTasks);
|
||||
maintenanceWorkers = Math.max(0, maintenanceWorkers);
|
||||
closingEngines = Math.max(0, closingEngines);
|
||||
pendingRegistrations = Math.max(0, pendingRegistrations);
|
||||
heapUsage = finiteRatio(heapUsage);
|
||||
reclaimUrgency = finiteRatio(reclaimUrgency);
|
||||
caches = caches == null ? CacheSnapshot.EMPTY : caches;
|
||||
pregenerator = pregenerator == null ? PregenSnapshot.INACTIVE : pregenerator;
|
||||
}
|
||||
|
||||
private static double finiteRatio(double value) {
|
||||
return Double.isFinite(value) ? Math.max(0D, Math.min(1D, value)) : 0D;
|
||||
}
|
||||
|
||||
record CacheSnapshot(
|
||||
CacheBucket total,
|
||||
CacheBucket resource,
|
||||
CacheBucket stream2d,
|
||||
CacheBucket stream3d,
|
||||
CacheBucket other
|
||||
) {
|
||||
static final CacheSnapshot EMPTY = new CacheSnapshot(
|
||||
CacheBucket.EMPTY,
|
||||
CacheBucket.EMPTY,
|
||||
CacheBucket.EMPTY,
|
||||
CacheBucket.EMPTY,
|
||||
CacheBucket.EMPTY
|
||||
);
|
||||
|
||||
CacheSnapshot {
|
||||
total = total == null ? CacheBucket.EMPTY : total;
|
||||
resource = resource == null ? CacheBucket.EMPTY : resource;
|
||||
stream2d = stream2d == null ? CacheBucket.EMPTY : stream2d;
|
||||
stream3d = stream3d == null ? CacheBucket.EMPTY : stream3d;
|
||||
other = other == null ? CacheBucket.EMPTY : other;
|
||||
}
|
||||
}
|
||||
|
||||
record CacheBucket(int count, long entries, long capacity) {
|
||||
static final CacheBucket EMPTY = new CacheBucket(0, 0L, 0L);
|
||||
|
||||
CacheBucket {
|
||||
count = Math.max(0, count);
|
||||
entries = Math.max(0L, entries);
|
||||
capacity = Math.max(0L, capacity);
|
||||
}
|
||||
|
||||
double usage() {
|
||||
return capacity <= 0L ? 0D : Math.min(1D, entries / (double) capacity);
|
||||
}
|
||||
|
||||
CacheBucket plus(CacheBucket other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
return new CacheBucket(count + other.count, entries + other.entries, capacity + other.capacity);
|
||||
}
|
||||
}
|
||||
|
||||
record PregenSnapshot(
|
||||
boolean active,
|
||||
String worldIdentity,
|
||||
String worldName,
|
||||
boolean paused,
|
||||
double progressPercent,
|
||||
long generated,
|
||||
long total,
|
||||
long remaining,
|
||||
double chunksPerSecond,
|
||||
long etaMs,
|
||||
long elapsedMs,
|
||||
long failed
|
||||
) {
|
||||
static final PregenSnapshot INACTIVE = new PregenSnapshot(
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
0D,
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
0D,
|
||||
0L,
|
||||
0L,
|
||||
0L
|
||||
);
|
||||
|
||||
PregenSnapshot {
|
||||
worldIdentity = worldIdentity == null ? "" : worldIdentity.trim();
|
||||
worldName = worldName == null ? "" : worldName.trim();
|
||||
progressPercent = Double.isFinite(progressPercent)
|
||||
? Math.max(0D, Math.min(100D, progressPercent))
|
||||
: 0D;
|
||||
generated = Math.max(0L, generated);
|
||||
total = Math.max(0L, total);
|
||||
remaining = Math.max(0L, remaining);
|
||||
chunksPerSecond = Double.isFinite(chunksPerSecond) ? Math.max(0D, chunksPerSecond) : 0D;
|
||||
etaMs = Math.max(0L, etaMs);
|
||||
elapsedMs = Math.max(0L, elapsedMs);
|
||||
failed = Math.max(0L, failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.ExperienceOrb;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
final class TreeFellerPresentation {
|
||||
private static final int MIN_BLOCKS_PER_PULSE = 4;
|
||||
private static final int MAX_BLOCKS_PER_PULSE = 64;
|
||||
private static final int TARGET_EROSION_PULSES = 60;
|
||||
private static final int MAX_EFFECT_ORIGINS_PER_PULSE = 16;
|
||||
|
||||
private final Player player;
|
||||
private final World sourceWorld;
|
||||
private final Queue<PendingDrop> pendingDrops = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicBoolean flushScheduled = new AtomicBoolean();
|
||||
private final AtomicBoolean fallbackScheduled = new AtomicBoolean();
|
||||
private final AtomicBoolean completed = new AtomicBoolean();
|
||||
private final AtomicBoolean effectFailureReported = new AtomicBoolean();
|
||||
private final AtomicBoolean deliveryFailureReported = new AtomicBoolean();
|
||||
private final AtomicInteger deliveryPulses = new AtomicInteger();
|
||||
private volatile Location fallbackLocation;
|
||||
|
||||
TreeFellerPresentation(Player player, World sourceWorld, Location fallbackLocation) {
|
||||
this.player = player;
|
||||
this.sourceWorld = sourceWorld;
|
||||
this.fallbackLocation = fallbackLocation.clone();
|
||||
}
|
||||
|
||||
static int blocksPerPulse(int blockCount) {
|
||||
int requested = Math.max(1, (blockCount + TARGET_EROSION_PULSES - 1) / TARGET_EROSION_PULSES);
|
||||
return Math.max(MIN_BLOCKS_PER_PULSE, Math.min(requested, MAX_BLOCKS_PER_PULSE));
|
||||
}
|
||||
|
||||
static int effectStride(int blocksPerPulse) {
|
||||
return Math.max(
|
||||
1,
|
||||
(blocksPerPulse + MAX_EFFECT_ORIGINS_PER_PULSE - 1) / MAX_EFFECT_ORIGINS_PER_PULSE
|
||||
);
|
||||
}
|
||||
|
||||
static List<ItemStack> consolidateDrops(Collection<ItemStack> drops) {
|
||||
List<ItemStack> consolidated = new ArrayList<>();
|
||||
for (ItemStack drop : drops) {
|
||||
mergeDrop(consolidated, drop);
|
||||
}
|
||||
return List.copyOf(consolidated);
|
||||
}
|
||||
|
||||
void activate(Block block) {
|
||||
try {
|
||||
Location center = block.getLocation().clone().add(0.5D, 0.5D, 0.5D);
|
||||
World world = block.getWorld();
|
||||
world.spawnParticle(Particle.ENCHANT, center, 24, 0.45D, 0.45D, 0.45D, 0.18D);
|
||||
world.spawnParticle(Particle.END_ROD, center, 8, 0.25D, 0.25D, 0.25D, 0.035D);
|
||||
world.playSound(center, Sound.BLOCK_ENCHANTMENT_TABLE_USE, 0.55F, 1.35F);
|
||||
world.playSound(center, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.4F, 0.8F);
|
||||
} catch (Throwable error) {
|
||||
reportEffectFailure(error);
|
||||
}
|
||||
}
|
||||
|
||||
void erode(
|
||||
Location source,
|
||||
BlockData visualData,
|
||||
int erosionOrder,
|
||||
int processed,
|
||||
int blocksPerPulse,
|
||||
int effectStride,
|
||||
int totalBlocks
|
||||
) {
|
||||
if (processed % effectStride != 0 || source.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
World world = source.getWorld();
|
||||
world.spawnParticle(Particle.BLOCK, source, 5, 0.3D, 0.3D, 0.3D, 0.04D, visualData);
|
||||
world.spawnParticle(Particle.ENCHANT, source, 3, 0.28D, 0.28D, 0.28D, 0.12D);
|
||||
if (processed % blocksPerPulse == 0) {
|
||||
double progress = totalBlocks <= 1 ? 1D : (double) erosionOrder / (double) (totalBlocks - 1);
|
||||
float pitch = (float) Math.min(1.95D, 0.65D + (progress * 1.25D));
|
||||
world.playSound(source, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.22F, pitch);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
reportEffectFailure(error);
|
||||
}
|
||||
}
|
||||
|
||||
boolean routeDrop(Object drop) {
|
||||
if (!(drop instanceof ItemStack item)) {
|
||||
return false;
|
||||
}
|
||||
if (item.getType() == Material.AIR || item.getAmount() <= 0) {
|
||||
return true;
|
||||
}
|
||||
return enqueue(new PendingDrop(item.clone(), 0));
|
||||
}
|
||||
|
||||
boolean routeExperience(int experience) {
|
||||
return experience <= 0 || enqueue(new PendingDrop(null, experience));
|
||||
}
|
||||
|
||||
void finish() {
|
||||
completed.set(true);
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
scheduleFlush();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enqueue(PendingDrop drop) {
|
||||
pendingDrops.add(drop);
|
||||
if (scheduleFlush()) {
|
||||
return true;
|
||||
}
|
||||
return !pendingDrops.remove(drop);
|
||||
}
|
||||
|
||||
private boolean scheduleFlush() {
|
||||
if (pendingDrops.isEmpty() || !flushScheduled.compareAndSet(false, true)) {
|
||||
return true;
|
||||
}
|
||||
fallbackScheduled.set(false);
|
||||
Runnable retired = () -> {
|
||||
if (!scheduleFallbackFlush()) {
|
||||
flushScheduled.set(false);
|
||||
IrisLogging.error("Unable to deliver queued Iris tree-feller drops after the player retired.");
|
||||
}
|
||||
};
|
||||
if (J.runEntity(player, this::flushAtPlayer, 1, retired)) {
|
||||
return true;
|
||||
}
|
||||
return scheduleFallbackFlush();
|
||||
}
|
||||
|
||||
private boolean scheduleFallbackFlush() {
|
||||
if (!fallbackScheduled.compareAndSet(false, true)) {
|
||||
return true;
|
||||
}
|
||||
Location fallback = fallbackLocation.clone();
|
||||
if (J.runAt(fallback, () -> flushAtFallback(fallback), 1)) {
|
||||
return true;
|
||||
}
|
||||
fallbackScheduled.set(false);
|
||||
flushScheduled.set(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void flushAtPlayer() {
|
||||
if (!player.isOnline() || !player.getWorld().equals(sourceWorld)) {
|
||||
if (!scheduleFallbackFlush()) {
|
||||
IrisLogging.error("Unable to deliver queued Iris tree-feller drops at their fallback location.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Location target = player.getLocation().clone().add(0D, 0.15D, 0D);
|
||||
fallbackLocation = target.clone();
|
||||
deliverBatch(target, drainPendingDrops(), true);
|
||||
completeFlush();
|
||||
}
|
||||
|
||||
private void flushAtFallback(Location fallback) {
|
||||
deliverBatch(fallback, drainPendingDrops(), false);
|
||||
completeFlush();
|
||||
}
|
||||
|
||||
private DropBatch drainPendingDrops() {
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
long experience = 0L;
|
||||
PendingDrop pending;
|
||||
while ((pending = pendingDrops.poll()) != null) {
|
||||
if (pending.item() != null) {
|
||||
items.add(pending.item());
|
||||
}
|
||||
experience = Math.min(Integer.MAX_VALUE, experience + pending.experience());
|
||||
}
|
||||
return new DropBatch(consolidateDrops(items), (int) experience);
|
||||
}
|
||||
|
||||
private void deliverBatch(Location target, DropBatch batch, boolean atPlayer) {
|
||||
World world = target.getWorld();
|
||||
if (world == null) {
|
||||
requeueBatch(batch);
|
||||
return;
|
||||
}
|
||||
int deliveredItems = 0;
|
||||
for (int index = 0; index < batch.items().size(); index++) {
|
||||
ItemStack item = batch.items().get(index);
|
||||
Item dropped;
|
||||
try {
|
||||
dropped = world.dropItem(target, item);
|
||||
} catch (Throwable error) {
|
||||
requeueUndelivered(batch, index);
|
||||
reportDeliveryFailure(error);
|
||||
return;
|
||||
}
|
||||
deliveredItems++;
|
||||
try {
|
||||
dropped.setVelocity(new Vector(0D, 0.08D, 0D));
|
||||
} catch (Throwable error) {
|
||||
reportEffectFailure(error);
|
||||
}
|
||||
}
|
||||
if (batch.experience() > 0) {
|
||||
try {
|
||||
ExperienceOrb orb = world.spawn(target.clone().add(0D, 0.35D, 0D), ExperienceOrb.class);
|
||||
orb.setExperience(batch.experience());
|
||||
} catch (Throwable error) {
|
||||
pendingDrops.add(new PendingDrop(null, batch.experience()));
|
||||
reportDeliveryFailure(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
int enchantParticles = Math.min(32, 6 + (deliveredItems * 2));
|
||||
Location effectLocation = target.clone().add(0D, 0.35D, 0D);
|
||||
world.spawnParticle(Particle.ENCHANT, effectLocation, enchantParticles, 0.35D, 0.25D, 0.35D, 0.15D);
|
||||
world.spawnParticle(Particle.END_ROD, effectLocation, Math.min(8, 2 + deliveredItems), 0.2D, 0.2D, 0.2D, 0.025D);
|
||||
int deliveryPulse = deliveryPulses.incrementAndGet();
|
||||
if (atPlayer && (deliveryPulse == 1 || deliveryPulse % 4 == 0 || completed.get())) {
|
||||
world.playSound(target, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.2F, 1.65F);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
reportEffectFailure(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeFlush() {
|
||||
fallbackScheduled.set(false);
|
||||
flushScheduled.set(false);
|
||||
if (!pendingDrops.isEmpty() && !scheduleFlush()) {
|
||||
IrisLogging.error("Unable to schedule the remaining Iris tree-feller drops.");
|
||||
}
|
||||
}
|
||||
|
||||
private void requeueBatch(DropBatch batch) {
|
||||
requeueUndelivered(batch, 0);
|
||||
}
|
||||
|
||||
private void requeueUndelivered(DropBatch batch, int firstUndeliveredItem) {
|
||||
for (int index = firstUndeliveredItem; index < batch.items().size(); index++) {
|
||||
pendingDrops.add(new PendingDrop(batch.items().get(index), 0));
|
||||
}
|
||||
if (batch.experience() > 0) {
|
||||
pendingDrops.add(new PendingDrop(null, batch.experience()));
|
||||
}
|
||||
}
|
||||
|
||||
private void reportEffectFailure(Throwable error) {
|
||||
if (effectFailureReported.compareAndSet(false, true)) {
|
||||
IrisLogging.reportError("Failed to render an Iris tree-feller effect.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportDeliveryFailure(Throwable error) {
|
||||
if (deliveryFailureReported.compareAndSet(false, true)) {
|
||||
IrisLogging.reportError("Failed to deliver an Iris tree-feller drop; it remains queued for retry.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void mergeDrop(List<ItemStack> consolidated, ItemStack source) {
|
||||
if (source == null || source.getType() == Material.AIR || source.getAmount() <= 0) {
|
||||
return;
|
||||
}
|
||||
int remaining = source.getAmount();
|
||||
for (ItemStack existing : consolidated) {
|
||||
if (!existing.isSimilar(source) || existing.getAmount() >= existing.getMaxStackSize()) {
|
||||
continue;
|
||||
}
|
||||
int transferable = Math.min(remaining, existing.getMaxStackSize() - existing.getAmount());
|
||||
existing.setAmount(existing.getAmount() + transferable);
|
||||
remaining -= transferable;
|
||||
if (remaining == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int maximumStackSize = Math.max(1, source.getMaxStackSize());
|
||||
while (remaining > 0) {
|
||||
ItemStack stack = source.clone();
|
||||
stack.setAmount(Math.min(remaining, maximumStackSize));
|
||||
consolidated.add(stack);
|
||||
remaining -= stack.getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
private record PendingDrop(ItemStack item, int experience) {
|
||||
}
|
||||
|
||||
private record DropBatch(List<ItemStack> items, int experience) {
|
||||
}
|
||||
}
|
||||
+1110
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,10 @@ load: STARTUP
|
||||
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||
website: volmit.com
|
||||
description: More than a Dimension!
|
||||
permissions:
|
||||
iris.treefeller:
|
||||
description: Allows survival players to fell Iris-managed trees with an axe.
|
||||
default: op
|
||||
dependencies:
|
||||
server:
|
||||
PlaceholderAPI:
|
||||
|
||||
@@ -22,7 +22,11 @@ loadbefore: [ Multiverse-Core ]
|
||||
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||
website: volmit.com
|
||||
description: More than a Dimension!
|
||||
commands:
|
||||
iris:
|
||||
aliases: [ ir, irs ]
|
||||
commands:
|
||||
iris:
|
||||
aliases: [ ir, irs ]
|
||||
permissions:
|
||||
iris.treefeller:
|
||||
description: Allows survival players to fell Iris-managed trees with an axe.
|
||||
default: op
|
||||
api-version: '${apiVersion}'
|
||||
|
||||
Reference in New Issue
Block a user