Skip to content

Commit e25627e

Browse files
committed
Gate dropped label visibility optimizations
1 parent b93d990 commit e25627e

11 files changed

Lines changed: 470 additions & 64 deletions

File tree

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

Lines changed: 138 additions & 54 deletions
Large diffs are not rendered by default.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* This file is part of InteractionVisualizer.
3+
*
4+
* Copyright (C) 2026. Contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*/
11+
12+
package com.loohp.interactionvisualizer.entities;
13+
14+
/**
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.
18+
*/
19+
record DroppedItemVisibilityPolicy(
20+
int viewDistance,
21+
boolean rateLimitEnabled,
22+
int bucketSize,
23+
int restorePerTick
24+
) {
25+
26+
static final int DEFAULT_BUCKET_SIZE = 128;
27+
static final int DEFAULT_RESTORE_PER_TICK = 32;
28+
static final int DEFAULT_VIEW_DISTANCE = 64;
29+
private static final int MIN_VIEW_DISTANCE = 8;
30+
private static final int MAX_VIEW_DISTANCE = 512;
31+
32+
static DroppedItemVisibilityPolicy create(boolean cullingEnabled,
33+
int configuredViewDistance,
34+
boolean rateLimitEnabled,
35+
int configuredBucketSize,
36+
int configuredRestorePerTick) {
37+
int requestedViewDistance = configuredViewDistance > 0
38+
? configuredViewDistance
39+
: DEFAULT_VIEW_DISTANCE;
40+
int viewDistance = cullingEnabled
41+
? Math.max(MIN_VIEW_DISTANCE, Math.min(MAX_VIEW_DISTANCE, requestedViewDistance))
42+
: 0;
43+
int bucketSize = configuredBucketSize > 0
44+
? configuredBucketSize
45+
: DEFAULT_BUCKET_SIZE;
46+
int restorePerTick = configuredRestorePerTick > 0
47+
? configuredRestorePerTick
48+
: DEFAULT_RESTORE_PER_TICK;
49+
return new DroppedItemVisibilityPolicy(
50+
viewDistance, rateLimitEnabled, bucketSize, restorePerTick);
51+
}
52+
53+
static DroppedItemVisibilityPolicy legacyDefaults() {
54+
return create(false, DEFAULT_VIEW_DISTANCE, false,
55+
DEFAULT_BUCKET_SIZE, DEFAULT_RESTORE_PER_TICK);
56+
}
57+
58+
boolean cullingEnabled() {
59+
return viewDistance > 0;
60+
}
61+
62+
boolean controlsPerViewerVisibility() {
63+
return cullingEnabled() || rateLimitEnabled;
64+
}
65+
66+
int effectiveViewDistance(int trackingDistance) {
67+
if (!cullingEnabled()) {
68+
throw new IllegalStateException("View-distance culling is disabled");
69+
}
70+
return Math.min(viewDistance, Math.max(1, trackingDistance));
71+
}
72+
73+
float labelViewRange() {
74+
if (!cullingEnabled()) {
75+
return 1.0F;
76+
}
77+
return (float) Math.max(0.125D, Math.min(8.0D, viewDistance / 64.0D));
78+
}
79+
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ void cancel(T value) {
4141
}
4242

4343
List<T> drain(int capacity, int refill, Predicate<T> stillWanted) {
44-
tokens = Math.min(Math.max(1, capacity), tokens + Math.max(0, refill));
44+
int safeCapacity = Math.max(1, capacity);
45+
long replenished = (long) tokens + Math.max(0, refill);
46+
tokens = (int) Math.min(safeCapacity, replenished);
4547
if (tokens == 0 || pending.isEmpty()) {
4648
return List.of();
4749
}
@@ -58,6 +60,21 @@ List<T> drain(int capacity, int refill, Predicate<T> stillWanted) {
5860
return ready;
5961
}
6062

63+
List<T> drainAll(Predicate<T> stillWanted) {
64+
if (pending.isEmpty()) {
65+
return List.of();
66+
}
67+
68+
List<T> ready = new ArrayList<>(pending.size());
69+
while (!pending.isEmpty()) {
70+
T value = pending.removeFirst();
71+
if (queued.remove(value) && stillWanted.test(value)) {
72+
ready.add(value);
73+
}
74+
}
75+
return ready;
76+
}
77+
6178
void clear() {
6279
pending.clear();
6380
queued.clear();

common/src/main/java/com/loohp/interactionvisualizer/managers/PerformanceMetrics.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public final class PerformanceMetrics implements Listener {
4545
private boolean configVisibilityRateLimit;
4646
private int configVisibilityBucketSize;
4747
private int configVisibilityRestorePerTick;
48+
private DroppedLabelVisibilityConfig droppedLabelVisibilityConfig =
49+
new DroppedLabelVisibilityConfig(false, 64, false, 128, 32);
50+
private DroppedLabelVisibilityConfig configDroppedLabelVisibility = droppedLabelVisibilityConfig;
4851
private boolean configEventDrivenBlockUpdates;
4952
private int configBlockUpdateMaxDirtyPerTick;
5053
private long startedNanos;
@@ -82,6 +85,15 @@ public static boolean isCollecting() {
8285
return INSTANCE.collecting;
8386
}
8487

88+
public static void droppedLabelVisibilityConfig(boolean cullingEnabled,
89+
int viewDistance,
90+
boolean rateLimitEnabled,
91+
int bucketSize,
92+
int restorePerTick) {
93+
INSTANCE.droppedLabelVisibilityConfig = new DroppedLabelVisibilityConfig(
94+
cullingEnabled, viewDistance, rateLimitEnabled, bucketSize, restorePerTick);
95+
}
96+
8597
public static boolean start(String requestedLabel) {
8698
if (INSTANCE.collecting) {
8799
return false;
@@ -92,6 +104,7 @@ public static boolean start(String requestedLabel) {
92104
INSTANCE.configVisibilityRateLimit = InteractionVisualizer.visibilityRateLimiting;
93105
INSTANCE.configVisibilityBucketSize = InteractionVisualizer.visibilityRateLimitBucketSize;
94106
INSTANCE.configVisibilityRestorePerTick = InteractionVisualizer.visibilityRateLimitRestorePerTick;
107+
INSTANCE.configDroppedLabelVisibility = INSTANCE.droppedLabelVisibilityConfig;
95108
INSTANCE.configEventDrivenBlockUpdates = InteractionVisualizer.eventDrivenBlockUpdates;
96109
INSTANCE.configBlockUpdateMaxDirtyPerTick = InteractionVisualizer.blockUpdateMaxDirtyPerTick;
97110
INSTANCE.startedNanos = System.nanoTime();
@@ -287,7 +300,8 @@ private Snapshot createSnapshot(LegacyTextComponentCache.CacheMetrics textCache)
287300
mean = samples == 0 ? 0.0D : mean / samples;
288301
long elapsedNanos = Math.max(1L, System.nanoTime() - startedNanos);
289302
return new Snapshot(label, configStaticAnchor, configPacketOnlyStatic, configVisibilityRateLimit,
290-
configVisibilityBucketSize, configVisibilityRestorePerTick, configEventDrivenBlockUpdates,
303+
configVisibilityBucketSize, configVisibilityRestorePerTick, configDroppedLabelVisibility,
304+
configEventDrivenBlockUpdates,
291305
configBlockUpdateMaxDirtyPerTick, LegacyTextComponentCache.isEnabled(),
292306
elapsedNanos, samples, tickSamplesDropped,
293307
percentile(sorted, 0.50D), percentile(sorted, 0.95D), percentile(sorted, 0.99D),
@@ -319,13 +333,22 @@ private static String sanitizeLabel(String value) {
319333
return sanitized.substring(0, Math.min(48, sanitized.length()));
320334
}
321335

336+
public record DroppedLabelVisibilityConfig(
337+
boolean cullingEnabled,
338+
int viewDistance,
339+
boolean rateLimitEnabled,
340+
int bucketSize,
341+
int restorePerTick) {
342+
}
343+
322344
public record Snapshot(
323345
String label,
324346
boolean staticAnchorDuringAnimation,
325347
boolean packetOnlyStatic,
326348
boolean visibilityRateLimit,
327349
int visibilityBucketSize,
328350
int visibilityRestorePerTick,
351+
DroppedLabelVisibilityConfig droppedLabelVisibility,
329352
boolean eventDrivenBlockUpdates,
330353
int blockUpdateMaxDirtyPerTick,
331354
boolean legacyTextComponentCache,
@@ -407,6 +430,10 @@ public String json() {
407430
"{\"label\":\"%s\",\"staticAnchorDuringAnimation\":%b," +
408431
"\"packetOnlyStatic\":%b,\"visibilityRateLimit\":%b," +
409432
"\"visibilityBucketSize\":%d,\"visibilityRestorePerTick\":%d," +
433+
"\"droppedLabelVisibilityCulling\":%b,\"droppedLabelViewDistance\":%d," +
434+
"\"droppedLabelVisibilityRateLimit\":%b," +
435+
"\"droppedLabelVisibilityBucketSize\":%d," +
436+
"\"droppedLabelVisibilityRestorePerTick\":%d," +
410437
"\"eventDrivenBlockUpdates\":%b,\"blockUpdateMaxDirtyPerTick\":%d," +
411438
"\"legacyTextComponentCache\":%b," +
412439
"\"seconds\":%.3f,\"tickSamples\":%d,\"observedTps\":%.6f," +
@@ -430,7 +457,10 @@ public String json() {
430457
"\"legacyTextCacheHits\":%d,\"legacyTextCacheHitRate\":%.6f," +
431458
"\"legacyTextSameRawFastPaths\":%d}",
432459
label, staticAnchorDuringAnimation, packetOnlyStatic, visibilityRateLimit,
433-
visibilityBucketSize, visibilityRestorePerTick, eventDrivenBlockUpdates,
460+
visibilityBucketSize, visibilityRestorePerTick,
461+
droppedLabelVisibility.cullingEnabled(), droppedLabelVisibility.viewDistance(),
462+
droppedLabelVisibility.rateLimitEnabled(), droppedLabelVisibility.bucketSize(),
463+
droppedLabelVisibility.restorePerTick(), eventDrivenBlockUpdates,
434464
blockUpdateMaxDirtyPerTick, legacyTextComponentCache,
435465
seconds(), tickSamples, observedTps(), droppedTickSamples,
436466
msptP50, msptP95, msptP99,

common/src/main/resources/config.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,15 @@ Entities:
281281
#This is in ticks (20 ticks = 1 second)
282282
#Setting this too low might impact performance
283283
UpdateRate: 20
284-
#Maximum distance in blocks at which a dropped-item label is kept active
285-
#Labels outside every eligible player's range are not spawned server-side
286-
ViewDistance: 64
287-
#Smooth label visibility bursts when entering areas with many dropped items
284+
#Experimental server-side distance culling for dropped-item labels
285+
VisibilityCulling:
286+
#Preserves the legacy label lifecycle unless explicitly enabled after validation
287+
Enabled: false
288+
ViewDistance: 64
289+
#Experimental smoothing for label visibility bursts
288290
VisibilityRateLimit:
291+
#Independent from Settings.Performance.VisibilityRateLimit and disabled by default
292+
Enabled: false
289293
BucketSize: 128
290294
RestorePerTick: 32
291295
#Paper does not expose its internal per-item despawn rate. This value
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* This file is part of InteractionVisualizer.
3+
*
4+
* Copyright (C) 2026. Contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*/
11+
12+
package com.loohp.interactionvisualizer.entities;
13+
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.nio.charset.StandardCharsets;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
class DroppedItemVisibilityPolicyTest {
26+
27+
@Test
28+
void disabledCullingAndLimiterPreserveLegacyVisibility() {
29+
DroppedItemVisibilityPolicy policy = DroppedItemVisibilityPolicy.create(false, 64, false, 128, 32);
30+
31+
assertFalse(policy.cullingEnabled());
32+
assertFalse(policy.rateLimitEnabled());
33+
assertFalse(policy.controlsPerViewerVisibility());
34+
assertEquals(1.0F, policy.labelViewRange());
35+
}
36+
37+
@Test
38+
void eachExperimentalControlCanEnablePerViewerVisibility() {
39+
DroppedItemVisibilityPolicy culling = DroppedItemVisibilityPolicy.create(true, 64, false, 128, 32);
40+
DroppedItemVisibilityPolicy limiting = DroppedItemVisibilityPolicy.create(false, 64, true, 128, 32);
41+
42+
assertTrue(culling.controlsPerViewerVisibility());
43+
assertTrue(limiting.controlsPerViewerVisibility());
44+
assertEquals(64, culling.effectiveViewDistance(96));
45+
assertEquals(48, culling.effectiveViewDistance(48));
46+
}
47+
48+
@Test
49+
void clampsDistanceAndRepairsInvalidBucketValues() {
50+
DroppedItemVisibilityPolicy minimum = DroppedItemVisibilityPolicy.create(true, 1, true, 0, -1);
51+
DroppedItemVisibilityPolicy maximum = DroppedItemVisibilityPolicy.create(true, 4096, true, 512, 64);
52+
DroppedItemVisibilityPolicy repaired = DroppedItemVisibilityPolicy.create(true, 0, false, 128, 32);
53+
54+
assertEquals(8, minimum.viewDistance());
55+
assertEquals(DroppedItemVisibilityPolicy.DEFAULT_BUCKET_SIZE, minimum.bucketSize());
56+
assertEquals(DroppedItemVisibilityPolicy.DEFAULT_RESTORE_PER_TICK, minimum.restorePerTick());
57+
assertEquals(512, maximum.viewDistance());
58+
assertEquals(8.0F, maximum.labelViewRange());
59+
assertEquals(DroppedItemVisibilityPolicy.DEFAULT_VIEW_DISTANCE, repaired.viewDistance());
60+
}
61+
62+
@Test
63+
void bundledConfigurationKeepsBothControlsDisabled() throws IOException {
64+
try (InputStream stream = getClass().getClassLoader().getResourceAsStream("config.yml")) {
65+
assertNotNull(stream);
66+
String config = new String(stream.readAllBytes(), StandardCharsets.UTF_8)
67+
.replace("\r\n", "\n");
68+
int itemOptions = config.indexOf(" Item:\n");
69+
int villager = config.indexOf(" Villager:\n", itemOptions);
70+
String droppedItemOptions = config.substring(itemOptions, villager);
71+
72+
assertTrue(droppedItemOptions.contains(
73+
" VisibilityCulling:\n"
74+
+ " #Preserves the legacy label lifecycle unless explicitly enabled after validation\n"
75+
+ " Enabled: false\n"
76+
+ " ViewDistance: 64\n"));
77+
assertTrue(droppedItemOptions.contains(
78+
" VisibilityRateLimit:\n"
79+
+ " #Independent from Settings.Performance.VisibilityRateLimit and disabled by default\n"
80+
+ " Enabled: false\n"));
81+
}
82+
}
83+
}

common/src/test/java/com/loohp/interactionvisualizer/entities/VisibilityTokenBucketTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,24 @@ void deduplicatesAndDropsCancelledOrStaleRequests() {
4343

4444
assertEquals(List.of(1), bucket.drain(4, 0, value -> value != 3));
4545
}
46+
47+
@Test
48+
void drainsImmediatelyWhenRateLimitingIsDisabled() {
49+
VisibilityTokenBucket<Integer> bucket = new VisibilityTokenBucket<>(0);
50+
bucket.request(1);
51+
bucket.request(2);
52+
bucket.request(3);
53+
bucket.cancel(2);
54+
55+
assertEquals(List.of(1, 3), bucket.drainAll(ignored -> true));
56+
assertEquals(List.of(), bucket.drainAll(ignored -> true));
57+
}
58+
59+
@Test
60+
void largeConfiguredLimitsDoNotOverflowTokenRefill() {
61+
VisibilityTokenBucket<Integer> bucket = new VisibilityTokenBucket<>(Integer.MAX_VALUE);
62+
bucket.request(1);
63+
64+
assertEquals(List.of(1), bucket.drain(Integer.MAX_VALUE, Integer.MAX_VALUE, ignored -> true));
65+
}
4666
}

common/src/test/java/com/loohp/interactionvisualizer/managers/PerformanceMetricsSlowestTickTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ void resetRestoresNoSampleSentinel() {
7676
@Test
7777
void snapshotJsonPublishesSlowestTickAttributionInMilliseconds() {
7878
PerformanceMetrics.Snapshot snapshot = new PerformanceMetrics.Snapshot(
79-
"diagnostic", false, false, false, 128, 32, true, 64, true,
79+
"diagnostic", false, false, false, 128, 32,
80+
new PerformanceMetrics.DroppedLabelVisibilityConfig(true, 64, true, 128, 32),
81+
true, 64, true,
8082
1_000_000_000L, 20, 0L,
8183
1.0D, 2.0D, 3.0D, 4.0D, 50.25D,
8284
4242, 1_783_951_200_123L, 7L, 12_345_678L,
@@ -93,6 +95,11 @@ void snapshotJsonPublishesSlowestTickAttributionInMilliseconds() {
9395
assertTrue(json.contains("\"msptMaxEndEpochMillis\":1783951200123"));
9496
assertTrue(json.contains("\"msptMaxBlockUpdateChecks\":7"));
9597
assertTrue(json.contains("\"msptMaxBlockUpdateMs\":12.345678"));
98+
assertTrue(json.contains("\"droppedLabelVisibilityCulling\":true"));
99+
assertTrue(json.contains("\"droppedLabelViewDistance\":64"));
100+
assertTrue(json.contains("\"droppedLabelVisibilityRateLimit\":true"));
101+
assertTrue(json.contains("\"droppedLabelVisibilityBucketSize\":128"));
102+
assertTrue(json.contains("\"droppedLabelVisibilityRestorePerTick\":32"));
96103
assertTrue(json.contains("\"legacyTextCacheHits\":95"));
97104
assertTrue(json.contains("\"legacyTextCacheHitRate\":0.950000"));
98105
assertTrue(json.contains("\"legacyTextSameRawFastPaths\":200"));
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
kind,group,run,filename,bytes,sha256
2+
frame_csv,frameview-factor-fixed-b1-true,1,FvSDKPerFrameStreamDataT202671422749.csv,6306563,d4320664ec2af8c203661c489a312715c45ab237fc75a3029dbd54914ef4a60b
3+
frame_csv,frameview-factor-fixed-b1-true,2,FvSDKPerFrameStreamDataT20267142280.csv,6720934,bc4a57880fd4b27aa3a38127285d49dabec660e14cd5fdb654d9b96057781e47
4+
frame_csv,frameview-factor-fixed-b1-true,3,FvSDKPerFrameStreamDataT202671422811.csv,6330701,f16dfd8ccae2677e684305efca6f4116e7b4b0526029e018a75d27867578c228
5+
frame_csv,frameview-factor-fixed-b1-true,4,FvSDKPerFrameStreamDataT202671422856.csv,6510632,90e2a0d973f0d8bc6c90362201f610ff42a65001d1d9b4cd246266aab76ac7cc
6+
frame_csv,frameview-factor-fixed-b1-true,5,FvSDKPerFrameStreamDataT20267142296.csv,6346557,ab8281084fbd67329252ce96dee8ea6c7aa613fba1e0711edd6708b3449beb42
7+
frame_csv,frameview-factor-fixed-b1-true,6,FvSDKPerFrameStreamDataT202671422917.csv,6448228,a4417984976612dc07100198e6eead419327260b770cd3436dbd0eb34ed1999a
8+
frame_csv,frameview-factor-fixed-a-false,1,FvSDKPerFrameStreamDataT202671423030.csv,5738113,a393cabfe2ea96a47bb5aad5f326758e3f9cb99a0ebfa98a19b40931a51c8455
9+
frame_csv,frameview-factor-fixed-a-false,2,FvSDKPerFrameStreamDataT202671423041.csv,5901362,32ffcb234ef262cdd70cc140b1b035e47c375faebdb7a3fd438b8788be431a4b
10+
frame_csv,frameview-factor-fixed-a-false,3,FvSDKPerFrameStreamDataT202671423052.csv,4896593,d2d3a1d033420096cc33275494ede656c4d090ffc6c437ad9bf52187fbbb69a2
11+
frame_csv,frameview-factor-fixed-a-false,4,FvSDKPerFrameStreamDataT202671423129.csv,5837042,9cb1cf1fd6eff142667be94056c8eff344ef51b75deef80628af51c7b674eec5
12+
frame_csv,frameview-factor-fixed-a-false,5,FvSDKPerFrameStreamDataT202671423140.csv,5829160,dfdfcce44636e62f95bf8b125fcb4c69f30ebd64c432e4f4c9276aacf52bc969
13+
frame_csv,frameview-factor-fixed-a-false,6,FvSDKPerFrameStreamDataT202671423151.csv,5952325,8460fb70fc46cae9c3e97e0ea601d16192e4ef44836b2c2874cf6c0acbb6a2e3
14+
frame_csv,frameview-factor-fixed-b2-true,1,FvSDKPerFrameStreamDataT202671423312.csv,6485056,b28d8a55d3fab5e17bc09fadf763e27f017b143cf4f2902b76fe285e33d48625
15+
frame_csv,frameview-factor-fixed-b2-true,2,FvSDKPerFrameStreamDataT202671423323.csv,6607308,e194516293101015ebc3eed0e95ff0afb7f1fd9956e2fbfe26690bc78dfb7487
16+
frame_csv,frameview-factor-fixed-b2-true,3,FvSDKPerFrameStreamDataT202671423333.csv,5771658,1a8a162135fa3a60de76bfc734c5154cec4024479c50284fe9d09a2c0a2c5925
17+
frame_csv,frameview-factor-fixed-b2-true,4,FvSDKPerFrameStreamDataT202671423414.csv,6661103,41e8c16f65488b7a95293f99adb59a7bfc7c88300152b3c37d66d7885075288b
18+
frame_csv,frameview-factor-fixed-b2-true,5,FvSDKPerFrameStreamDataT202671423425.csv,6624424,ee9488513fbf428c3bfa7e577700c8c4b594ba51d31832c24328f29fdb1e343a
19+
frame_csv,frameview-factor-fixed-b2-true,6,FvSDKPerFrameStreamDataT202671423436.csv,6548953,40a1672b2e0ab5d5bdfd448a51a79f1cd19229da89f199d54a97f599709b298e
20+
analysis_db,frameview-factor-fixed-summary,,performance-validation.sqlite,28672,6f7799fb095c0ad3fff2e4da3b2006cd470631df081f9df4abf863a054d48bcc

0 commit comments

Comments
 (0)