Skip to content

Commit 7050203

Browse files
committed
Enable Paper 26 performance defaults and lifecycle audit
1 parent 4e5b5b5 commit 7050203

26 files changed

Lines changed: 550 additions & 38 deletions

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ The production plugin JAR is written to `build/libs/InteractionVisualizer-<versi
6262
`check` includes unit tests, the Paper 26.1.2 compilation, and a second compile
6363
against Paper 26.2.
6464

65+
## Performance rollout and diagnostics
66+
67+
New installations enable the bounded visibility restore, dropped-label spatial
68+
culling, static packet items, static animation anchors, and coordinated block
69+
updates. Existing explicit `false` values remain unchanged and produce one
70+
migration reminder. Every path has its own rollback switch in `config.yml`;
71+
event-driven block updates require a restart, while the other switches reload.
72+
73+
Use `/iv perf start <label>` and `/iv perf stop` around a stable sample window.
74+
The resulting `IV_PERF` JSON includes viewer candidates/full reconciles,
75+
dropped-item spatial and full-scan candidates, block queues, preference I/O and
76+
SQL operations, packet operations, and anchor entity operations. Every plugin
77+
disable also emits `IV_PERF_SHUTDOWN`; `totalRetained` should be zero in repeated
78+
enable/disable leak tests.
79+
6580
## Optional integrations
6681

6782
- [CraftEngine](https://github.com/Xiao-MoMi/craft-engine) 26.7.2: optional

common/src/main/java/com/loohp/interactionvisualizer/InteractionVisualizer.java

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public class InteractionVisualizer extends JavaPlugin {
7777
public static final int BSTATS_PLUGIN_ID = 7024;
7878
public static final String CONFIG_ID = "config";
7979
public static final Set<String> SUPPORTED_MINECRAFT_VERSIONS = Set.of("26.1.2", "26.2");
80+
private static final String PERFORMANCE_MIGRATION_MARKER =
81+
".paper26-performance-defaults-advised";
8082

8183
public static InteractionVisualizer plugin = null;
8284

@@ -114,17 +116,17 @@ public class InteractionVisualizer extends JavaPlugin {
114116

115117
public static boolean defaultDisabledAll = false;
116118
/** A/B switch: virtual item remains authoritative while an invisible tracker stays stationary. */
117-
public static boolean staticVirtualItemAnchorsDuringAnimation = false;
119+
public static boolean staticVirtualItemAnchorsDuringAnimation = true;
118120
/** A/B switch: eligible stationary virtual items are tracked and rendered entirely by packets. */
119-
public static boolean packetOnlyStaticVirtualItems = false;
121+
public static boolean packetOnlyStaticVirtualItems = true;
120122
/** A/B switch: safe animated virtual items use Typewriter-style per-viewer packet tracking. */
121123
public static boolean packetOnlyAnimatedVirtualItems = false;
122124
/** A/B switch: smooths visibility recovery bursts; hides are always immediate. */
123-
public static boolean visibilityRateLimiting = false;
125+
public static boolean visibilityRateLimiting = true;
124126
public static int visibilityRateLimitBucketSize = 128;
125127
public static int visibilityRateLimitRestorePerTick = 32;
126128
/** A/B switch: coalesces block changes and updates tracked blocks from fixed-budget loops. */
127-
public static boolean eventDrivenBlockUpdates = false;
129+
public static boolean eventDrivenBlockUpdates = true;
128130
public static int blockUpdateMaxDirtyPerTick = 64;
129131

130132
private boolean blockUpdateModeInitialized;
@@ -181,14 +183,17 @@ public void onEnable() {
181183
if (!getDataFolder().exists()) {
182184
getDataFolder().mkdirs();
183185
}
186+
File configFile = new File(getDataFolder(), "config.yml");
187+
boolean existingConfiguration = configFile.isFile();
184188
try {
185-
Config.loadConfig(CONFIG_ID, new File(getDataFolder(), "config.yml"), getClass().getClassLoader().getResourceAsStream("config.yml"), getClass().getClassLoader().getResourceAsStream("config.yml"), true);
189+
Config.loadConfig(CONFIG_ID, configFile, getClass().getClassLoader().getResourceAsStream("config.yml"), getClass().getClassLoader().getResourceAsStream("config.yml"), true);
186190
} catch (IOException e) {
187191
e.printStackTrace();
188192
getServer().getPluginManager().disablePlugin(this);
189193
return;
190194
}
191195
loadConfig();
196+
advisePerformanceMigrationOnce(existingConfiguration);
192197

193198
boolean craftEngineHooked = CustomContentManager.initialize(this).contains("craftengine");
194199
if (isPluginEnabled("CraftEngine")) {
@@ -267,25 +272,41 @@ public void onEnable() {
267272
@Override
268273
public void onDisable() {
269274
shutdownPerformanceScenes();
270-
if (lightManager != null) {
271-
lightManager.shutdown();
275+
int retainedLightState = 0;
276+
ILightManager disablingLightManager = lightManager;
277+
if (disablingLightManager != null) {
278+
disablingLightManager.shutdown();
279+
retainedLightState = disablingLightManager.retainedStateCount();
272280
lightManager = ILightManager.DUMMY_INSTANCE;
273281
}
274-
viewerCullingManager.shutdown();
282+
ViewerCullingManager disablingCullingManager = viewerCullingManager;
283+
disablingCullingManager.shutdown();
284+
int retainedCullingRegistrations = disablingCullingManager.retainedRegistrations();
275285
viewerCullingManager = ViewerCullingManager.DISABLED;
276286
CustomContentManager.shutdown();
277-
if (preferenceManager != null) {
278-
preferenceManager.close();
287+
int retainedPreferenceState = 0;
288+
PreferenceManager disablingPreferenceManager = preferenceManager;
289+
if (disablingPreferenceManager != null) {
290+
disablingPreferenceManager.close();
291+
retainedPreferenceState = disablingPreferenceManager.retainedStateCount();
292+
preferenceManager = null;
279293
}
280294
Database.close();
281295
TaskManager.shutdown();
282296
DisplayManager.shutdown();
283297
TileEntityManager.shutdown();
284298
PlayerLocationManager.clearCache();
299+
playerTrackingRange.clear();
285300
LegacyTextComponentCache.invalidateAll();
286-
if (asyncExecutorManager != null) {
287-
asyncExecutorManager.close();
301+
int retainedAsyncTasks = 0;
302+
AsyncExecutorManager disablingAsyncExecutorManager = asyncExecutorManager;
303+
if (disablingAsyncExecutorManager != null) {
304+
disablingAsyncExecutorManager.close();
305+
retainedAsyncTasks = disablingAsyncExecutorManager.retainedTaskCount();
306+
asyncExecutorManager = null;
288307
}
308+
PerformanceMetrics.logShutdownState(this, retainedAsyncTasks,
309+
retainedPreferenceState, retainedLightState, retainedCullingRegistrations);
289310
getServer().getConsoleSender().sendMessage(Component.text(
290311
"[InteractionVisualizer] Disabled; all display entities removed.", NamedTextColor.RED));
291312
}
@@ -321,6 +342,43 @@ public SparrowConfiguration getConfiguration() {
321342
return Config.getConfig(CONFIG_ID).getConfiguration();
322343
}
323344

345+
private void advisePerformanceMigrationOnce(boolean existingConfiguration) {
346+
File marker = new File(getDataFolder(), PERFORMANCE_MIGRATION_MARKER);
347+
if (marker.isFile()) {
348+
return;
349+
}
350+
try {
351+
if (!marker.createNewFile()) {
352+
return;
353+
}
354+
} catch (IOException exception) {
355+
getLogger().log(Level.WARNING,
356+
"Unable to persist the Paper 26 performance migration notice marker", exception);
357+
}
358+
if (existingConfiguration && hasPreservedPerformanceOptOut()) {
359+
getLogger().warning("Paper 26 performance defaults are enabled for new installations. "
360+
+ "Your explicit false values were preserved; review StaticAnchorDuringAnimation, "
361+
+ "PacketOnlyStatic, VisibilityRateLimit, BlockUpdates.EventDriven, and dropped-item "
362+
+ "VisibilityCulling/VisibilityRateLimit when you are ready to migrate.");
363+
}
364+
}
365+
366+
private boolean hasPreservedPerformanceOptOut() {
367+
SparrowConfiguration configuration = getConfiguration();
368+
return !configuration.getBoolean(
369+
"Settings.Performance.VirtualItems.StaticAnchorDuringAnimation")
370+
|| !configuration.getBoolean(
371+
"Settings.Performance.VirtualItems.PacketOnlyStatic")
372+
|| !configuration.getBoolean(
373+
"Settings.Performance.VisibilityRateLimit.Enabled")
374+
|| !configuration.getBoolean(
375+
"Settings.Performance.BlockUpdates.EventDriven")
376+
|| !configuration.getBoolean(
377+
"Entities.Item.Options.VisibilityCulling.Enabled")
378+
|| !configuration.getBoolean(
379+
"Entities.Item.Options.VisibilityRateLimit.Enabled");
380+
}
381+
324382
public void loadConfig() {
325383
Config config = Config.getConfig(CONFIG_ID);
326384
config.reload();

common/src/main/java/com/loohp/interactionvisualizer/blocks/EnderchestDisplay.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ public class EnderchestDisplay implements Listener, VisualizerDisplay {
6363
public static ConcurrentHashMap<Player, List<Item>> link = new ConcurrentHashMap<>();
6464
public static ConcurrentHashMap<Player, Block> playermap = new ConcurrentHashMap<>();
6565

66+
public static void shutdown() {
67+
link.clear();
68+
playermap.clear();
69+
}
70+
71+
public static int retainedStateCount() {
72+
return link.size() + playermap.size();
73+
}
74+
6675
@Override
6776
public EntryKey key() {
6877
return KEY;

common/src/main/java/com/loohp/interactionvisualizer/database/Database.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ public static void close() {
199199
}
200200
}
201201

202+
public static int retainedConnectionCount() {
203+
synchronized (DATABASE_LOCK) {
204+
return connection == null ? 0 : 1;
205+
}
206+
}
207+
202208
/** Compatibility hook for older internal callers. */
203209
public static void runExclusive(Runnable operation) {
204210
synchronized (DATABASE_LOCK) {

common/src/main/java/com/loohp/interactionvisualizer/debug/PerformanceBlockScene.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,10 @@ public static ShutdownReport shutdown() {
462462
restoreFailures, inspectionFailures);
463463
}
464464

465+
public static int retainedStateCount() {
466+
return scenes.size();
467+
}
468+
465469
private static Snapshot mutate(Session session, int requestedOperations, Mode mode) {
466470
Objects.requireNonNull(mode, "mode");
467471
int operations = Math.max(0, Math.min(session.entries.size(), requestedOperations));

common/src/main/java/com/loohp/interactionvisualizer/debug/PerformanceScene.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ public static void shutdown() {
142142
}
143143
}
144144

145+
public static int retainedStateCount() {
146+
return scenes.size();
147+
}
148+
145149
private static void expire(UUID ownerId, Set<VisualizerEntity> entities) {
146150
if (scenes.remove(ownerId, entities)) {
147151
removeEntities(entities);

common/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemDisplay.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ private void tickAllInternal() {
321321
}
322322
update(tracked, itemIndex, viewerIndex, remainingWorldItems);
323323
}
324+
if (viewerIndex != null) {
325+
PerformanceMetrics.droppedViewerDistanceChecks(viewerIndex.candidateChecks());
326+
}
324327
if (visibilityPolicy.controlsPerViewerVisibility()) {
325328
DroppedItemVisibilityIndex<UUID> visibilityIndex = null;
326329
if (visibilityPolicy.cullingEnabled()) {
@@ -549,9 +552,11 @@ private void reconcileLabelVisibility(Collection<Player> viewers, List<TrackedIt
549552
? visibilityPolicy.effectiveViewDistance(trackingDistance)
550553
: 0.0D;
551554
if (visibilityIndex != null) {
552-
visibilityIndex.queryInto(player.getWorld().getUID(),
555+
int candidates = visibilityIndex.queryInto(player.getWorld().getUID(),
553556
playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(), range, desired);
557+
PerformanceMetrics.droppedSpatialCandidates(candidates);
554558
} else {
559+
PerformanceMetrics.droppedFullScanCandidates(validItems.size());
555560
for (TrackedItem tracked : validItems) {
556561
TextDisplay label = labels.get(tracked.itemId());
557562
if (label != null && label.isValid()

common/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemSpatialIndex.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ boolean hasViewerWithin(UUID worldId, double x, double y, double z,
132132
double[] zCoordinates = viewers.zCoordinates;
133133
int viewerCount = viewers.size;
134134
for (int index = 0; index < viewerCount; index++) {
135+
viewers.candidateChecks++;
135136
double deltaX = xCoordinates[index] - x;
136137
double deltaY = yCoordinates[index] - y;
137138
double deltaZ = zCoordinates[index] - z;
@@ -195,6 +196,17 @@ boolean hasActiveBounds(UUID worldId) {
195196
return viewers != null && viewers.boundsActive;
196197
}
197198

199+
long candidateChecks() {
200+
if (viewersByWorld == null) {
201+
return singleWorldViewers == null ? 0L : singleWorldViewers.candidateChecks;
202+
}
203+
long checks = 0L;
204+
for (WorldViewerBucket viewers : viewersByWorld.values()) {
205+
checks += viewers.candidateChecks;
206+
}
207+
return checks;
208+
}
209+
198210
private static final class WorldViewerBucket {
199211

200212
private static final int INITIAL_CAPACITY = 8;
@@ -225,6 +237,7 @@ private static final class WorldViewerBucket {
225237
private int expensiveQueries;
226238
private int gridQueriesUntilProbe;
227239
private int size;
240+
private long candidateChecks;
228241

229242
private WorldViewerBucket(int initialCapacity) {
230243
if (initialCapacity > 0) {
@@ -333,6 +346,7 @@ private int firstViewerWithin(double x, double y, double z, double rangeSquared)
333346
double[] zCoordinates = this.zCoordinates;
334347
int viewerCount = size;
335348
for (int index = 0; index < viewerCount; index++) {
349+
candidateChecks++;
336350
double deltaX = xCoordinates[index] - x;
337351
double deltaY = yCoordinates[index] - y;
338352
double deltaZ = zCoordinates[index] - z;
@@ -352,6 +366,7 @@ private boolean hasViewerWithinGrid(double x, double y, double z, double rangeSq
352366
List<Point> points = viewerGrid.get(new ViewerGridCell(cellX, cellZ));
353367
if (points != null) {
354368
for (Point point : points) {
369+
candidateChecks++;
355370
double deltaX = point.x() - x;
356371
double deltaY = point.y() - y;
357372
double deltaZ = point.z() - z;

common/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemVisibilityPolicy.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
package com.loohp.interactionvisualizer.entities;
1313

1414
/**
15-
* Validated configuration for the opt-in dropped-item visibility controls.
16-
* Disabled culling preserves the legacy server-side label lifecycle;
17-
* rate limiting is independently opt-in.
15+
* Validated configuration for the independently reversible dropped-item
16+
* visibility controls. Disabled culling preserves the legacy server-side
17+
* label lifecycle; rate limiting has its own rollback switch.
1818
*/
1919
record DroppedItemVisibilityPolicy(
2020
int viewDistance,

common/src/main/java/com/loohp/interactionvisualizer/integration/CustomContentManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public static synchronized void shutdown() {
6969
owner = null;
7070
}
7171

72+
public static synchronized int retainedStateCount() {
73+
return bridges.size() + failedProviders.size() + reportedRuntimeFailures.size()
74+
+ retryAfter.size() + (owner == null ? 0 : 1);
75+
}
76+
7277
public static Set<String> providers() {
7378
Set<String> providers = new LinkedHashSet<>();
7479
for (CustomContentBridge bridge : bridges) {

0 commit comments

Comments
 (0)