Skip to content

Commit 71fd3c2

Browse files
authored
Merge PR #5: Optimize Paper 26 display hot paths
Formal upstream, runtime, and packet AB campaigns passed before merge.
2 parents ad3e59b + c39b196 commit 71fd3c2

76 files changed

Lines changed: 6268 additions & 1268 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,33 @@ 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
6883
custom-item ID recognition. CraftEngine items can be selected by the third
6984
field of an item-label blacklist rule, and their display pose can be
70-
overridden in `material.yml` under `CustomItems`. CraftEngine is not bundled
71-
and the plugin behaves exactly as before when it is absent.
72-
- [LightAPI](https://www.spigotmc.org/resources/lightapi-fork.48247/)
85+
overridden in `material.yml` under `CustomItems`. It also supplies display
86+
lighting: InteractionVisualizer shares its light-block reference counts so
87+
CraftEngine furniture and IV displays do not remove each other's light, and
88+
performs no lighting task while idle. With `Settings.HideIfViewObstructed`,
89+
IV registers only its sent-chunk candidates in CraftEngine's entity-culling
90+
API for a second-stage ray-traced wall-occlusion check. CraftEngine is not
91+
bundled and the plugin behaves exactly as before when it is absent.
7392
- [OpenInv](https://dev.bukkit.org/projects/openinv)
7493
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/)
7594
- Essentials, SuperVanish, PremiumVanish, and CMI

build.gradle.kts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ dependencies {
3434
compileOnly("me.clip:placeholderapi:2.11.7")
3535
compileOnly("net.momirealms:craft-engine-core:$craftEngineVersion")
3636
compileOnly("net.momirealms:craft-engine-bukkit:$craftEngineVersion")
37-
compileOnly(files("common/lib/LightAPI-fork-3.5.2.jar"))
3837

3938
implementation("net.momirealms:sparrow-yaml:1.0.7")
4039
implementation("net.momirealms:sparrow-heart:$sparrowHeartVersion")
@@ -49,7 +48,6 @@ dependencies {
4948
paper26_2CompileClasspath("me.clip:placeholderapi:2.11.7")
5049
paper26_2CompileClasspath("net.momirealms:craft-engine-core:$craftEngineVersion")
5150
paper26_2CompileClasspath("net.momirealms:craft-engine-bukkit:$craftEngineVersion")
52-
paper26_2CompileClasspath(files("common/lib/LightAPI-fork-3.5.2.jar"))
5351
}
5452

5553
java {
@@ -224,15 +222,32 @@ val verifyCustomContentIsolation = tasks.register("verifyCustomContentIsolation"
224222
val allowedCraftEngineSource = file(
225223
"common/src/main/java/com/loohp/interactionvisualizer/integration/craftengine/CraftEngineCustomContentBridge.java",
226224
).canonicalFile
225+
val allowedCraftEngineLightSource = file(
226+
"common/src/main/java/com/loohp/interactionvisualizer/integration/craftengine/CraftEngineLightManager.java",
227+
).canonicalFile
228+
val allowedCraftEngineCullingSource = file(
229+
"common/src/main/java/com/loohp/interactionvisualizer/integration/craftengine/CraftEngineViewerCullingManager.java",
230+
).canonicalFile
227231
val managerSource = file(
228232
"common/src/main/java/com/loohp/interactionvisualizer/integration/CustomContentManager.java",
229233
).canonicalFile
234+
val lightLoaderSource = file(
235+
"common/src/main/java/com/loohp/interactionvisualizer/managers/LightManager.java",
236+
).canonicalFile
237+
val cullingLoaderSource = file(
238+
"common/src/main/java/com/loohp/interactionvisualizer/integration/ViewerCullingManagerLoader.java",
239+
).canonicalFile
230240
val stableApiClass = "net.momirealms.craftengine.bukkit.api.CraftEngineItems"
241+
val lightSentinelClass = "net.momirealms.craftengine.bukkit.api.BukkitAdaptor"
242+
val cullingSentinelClass = "net.momirealms.craftengine.core.entity.culling.Cullable"
231243
val craftEngineToken = Regex("net\\.momirealms\\.craftengine(?:\\.[A-Za-z_$][A-Za-z0-9_$]*)+")
232244
val violations = sources.files.flatMap { source ->
233245
craftEngineToken.findAll(source.readText()).mapNotNull { match ->
234246
val allowed = when (source.canonicalFile) {
235247
allowedCraftEngineSource, managerSource -> match.value == stableApiClass
248+
lightLoaderSource -> match.value == lightSentinelClass
249+
cullingLoaderSource -> match.value == cullingSentinelClass
250+
allowedCraftEngineLightSource, allowedCraftEngineCullingSource -> true
236251
else -> false
237252
}
238253
if (allowed) null else "${source.relativeTo(rootDir)}: ${match.value}"

common/lib/LightAPI-fork-3.5.2.jar

-131 KB
Binary file not shown.

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

Lines changed: 152 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.loohp.interactionvisualizer.debug.PerformanceBlockScene;
2727
import com.loohp.interactionvisualizer.debug.PerformanceScene;
2828
import com.loohp.interactionvisualizer.integration.CustomContentManager;
29+
import com.loohp.interactionvisualizer.integration.ViewerCullingManager;
30+
import com.loohp.interactionvisualizer.integration.ViewerCullingManagerLoader;
2931
import com.loohp.interactionvisualizer.managers.AsyncExecutorManager;
3032
import com.loohp.interactionvisualizer.managers.LangManager;
3133
import com.loohp.interactionvisualizer.managers.LightManager;
@@ -75,10 +77,15 @@ public class InteractionVisualizer extends JavaPlugin {
7577
public static final int BSTATS_PLUGIN_ID = 7024;
7678
public static final String CONFIG_ID = "config";
7779
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";
7882

7983
public static InteractionVisualizer plugin = null;
8084

85+
/** @deprecated LightAPI is no longer used; retained for binary compatibility. */
86+
@Deprecated(forRemoval = false)
8187
public static Boolean lightapi = false;
88+
public static Boolean craftEngineLight = false;
8289
public static Boolean openinv = false;
8390

8491
public static Set<String> exemptBlocks = new HashSet<>();
@@ -109,20 +116,25 @@ public class InteractionVisualizer extends JavaPlugin {
109116

110117
public static boolean defaultDisabledAll = false;
111118
/** A/B switch: virtual item remains authoritative while an invisible tracker stays stationary. */
112-
public static boolean staticVirtualItemAnchorsDuringAnimation = false;
119+
public static boolean staticVirtualItemAnchorsDuringAnimation = true;
113120
/** A/B switch: eligible stationary virtual items are tracked and rendered entirely by packets. */
114-
public static boolean packetOnlyStaticVirtualItems = false;
121+
public static boolean packetOnlyStaticVirtualItems = true;
122+
/** A/B switch: safe animated virtual items use Typewriter-style per-viewer packet tracking. */
123+
public static boolean packetOnlyAnimatedVirtualItems = false;
115124
/** A/B switch: smooths visibility recovery bursts; hides are always immediate. */
116-
public static boolean visibilityRateLimiting = false;
125+
public static boolean visibilityRateLimiting = true;
117126
public static int visibilityRateLimitBucketSize = 128;
118127
public static int visibilityRateLimitRestorePerTick = 32;
119128
/** A/B switch: coalesces block changes and updates tracked blocks from fixed-budget loops. */
120-
public static boolean eventDrivenBlockUpdates = false;
129+
public static boolean eventDrivenBlockUpdates = true;
121130
public static int blockUpdateMaxDirtyPerTick = 64;
122131

123132
private boolean blockUpdateModeInitialized;
133+
private boolean cullingLifecycleInitialized;
134+
private boolean cullingProviderWarningLogged;
124135

125136
public static ILightManager lightManager;
137+
public static ViewerCullingManager viewerCullingManager = ViewerCullingManager.DISABLED;
126138
public static PreferenceManager preferenceManager;
127139
public static AsyncExecutorManager asyncExecutorManager;
128140

@@ -160,18 +172,9 @@ public void onEnable() {
160172
new ThreadPoolExecutor.CallerRunsPolicy());
161173
asyncExecutorManager = new AsyncExecutorManager(threadPool);
162174

163-
if (isPluginEnabled("LightAPI")) {
164-
try {
165-
Class.forName("ru.beykerykt.lightapi.utils.Debug");
166-
hookMessage("LightAPI");
167-
lightapi = true;
168-
lightManager = new LightManager(this);
169-
} catch (ClassNotFoundException ignored) {
170-
}
171-
}
172-
if (!lightapi) {
173-
lightManager = ILightManager.DUMMY_INSTANCE;
174-
}
175+
lightapi = false;
176+
craftEngineLight = false;
177+
lightManager = ILightManager.DUMMY_INSTANCE;
175178
if (isPluginEnabled("OpenInv")) {
176179
hookMessage("OpenInv");
177180
openinv = true;
@@ -180,16 +183,32 @@ public void onEnable() {
180183
if (!getDataFolder().exists()) {
181184
getDataFolder().mkdirs();
182185
}
186+
File configFile = new File(getDataFolder(), "config.yml");
187+
boolean existingConfiguration = configFile.isFile();
183188
try {
184-
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);
185190
} catch (IOException e) {
186191
e.printStackTrace();
187192
getServer().getPluginManager().disablePlugin(this);
188193
return;
189194
}
190195
loadConfig();
191-
192-
if (CustomContentManager.initialize(this).contains("craftengine")) {
196+
advisePerformanceMigrationOnce(existingConfiguration);
197+
198+
boolean craftEngineHooked = CustomContentManager.initialize(this).contains("craftengine");
199+
if (isPluginEnabled("CraftEngine")) {
200+
ILightManager craftEngineManager = LightManager.createCraftEngine(this, lightUpdatePeriod).orElse(null);
201+
if (craftEngineManager != null) {
202+
lightManager = craftEngineManager;
203+
craftEngineLight = true;
204+
craftEngineHooked = true;
205+
}
206+
}
207+
if (configureViewerCulling()) {
208+
craftEngineHooked = true;
209+
}
210+
cullingLifecycleInitialized = true;
211+
if (craftEngineHooked) {
193212
hookMessage("CraftEngine");
194213
}
195214

@@ -253,18 +272,41 @@ public void onEnable() {
253272
@Override
254273
public void onDisable() {
255274
shutdownPerformanceScenes();
275+
int retainedLightState = 0;
276+
ILightManager disablingLightManager = lightManager;
277+
if (disablingLightManager != null) {
278+
disablingLightManager.shutdown();
279+
retainedLightState = disablingLightManager.retainedStateCount();
280+
lightManager = ILightManager.DUMMY_INSTANCE;
281+
}
282+
ViewerCullingManager disablingCullingManager = viewerCullingManager;
283+
disablingCullingManager.shutdown();
284+
int retainedCullingRegistrations = disablingCullingManager.retainedRegistrations();
285+
viewerCullingManager = ViewerCullingManager.DISABLED;
256286
CustomContentManager.shutdown();
257-
if (preferenceManager != null) {
258-
preferenceManager.close();
287+
int retainedPreferenceState = 0;
288+
PreferenceManager disablingPreferenceManager = preferenceManager;
289+
if (disablingPreferenceManager != null) {
290+
disablingPreferenceManager.close();
291+
retainedPreferenceState = disablingPreferenceManager.retainedStateCount();
292+
preferenceManager = null;
259293
}
294+
Database.close();
260295
TaskManager.shutdown();
261296
DisplayManager.shutdown();
262297
TileEntityManager.shutdown();
263298
PlayerLocationManager.clearCache();
299+
playerTrackingRange.clear();
264300
LegacyTextComponentCache.invalidateAll();
265-
if (asyncExecutorManager != null) {
266-
asyncExecutorManager.close();
301+
int retainedAsyncTasks = 0;
302+
AsyncExecutorManager disablingAsyncExecutorManager = asyncExecutorManager;
303+
if (disablingAsyncExecutorManager != null) {
304+
disablingAsyncExecutorManager.close();
305+
retainedAsyncTasks = disablingAsyncExecutorManager.retainedTaskCount();
306+
asyncExecutorManager = null;
267307
}
308+
PerformanceMetrics.logShutdownState(this, retainedAsyncTasks,
309+
retainedPreferenceState, retainedLightState, retainedCullingRegistrations);
268310
getServer().getConsoleSender().sendMessage(Component.text(
269311
"[InteractionVisualizer] Disabled; all display entities removed.", NamedTextColor.RED));
270312
}
@@ -300,6 +342,43 @@ public SparrowConfiguration getConfiguration() {
300342
return Config.getConfig(CONFIG_ID).getConfiguration();
301343
}
302344

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+
303382
public void loadConfig() {
304383
Config config = Config.getConfig(CONFIG_ID);
305384
config.reload();
@@ -326,8 +405,14 @@ public void loadConfig() {
326405

327406
disabledWorlds = new HashSet<>(getConfiguration().getStringList("Settings.DisabledWorlds"));
328407
hideIfObstructed = getConfiguration().getBoolean("Settings.HideIfViewObstructed");
408+
if (cullingLifecycleInitialized) {
409+
configureViewerCulling();
410+
}
329411

330412
lightUpdatePeriod = getConfiguration().getInt("LightUpdate.Period");
413+
if (lightManager != null) {
414+
lightManager.setUpdatePeriod(lightUpdatePeriod);
415+
}
331416

332417
updaterEnabled = getConfiguration().getBoolean("Options.Updater");
333418

@@ -341,6 +426,8 @@ public void loadConfig() {
341426
"Settings.Performance.VirtualItems.StaticAnchorDuringAnimation");
342427
packetOnlyStaticVirtualItems = getConfiguration().getBoolean(
343428
"Settings.Performance.VirtualItems.PacketOnlyStatic");
429+
packetOnlyAnimatedVirtualItems = getConfiguration().getBoolean(
430+
"Settings.Performance.VirtualItems.PacketOnlyAnimated");
344431
visibilityRateLimiting = getConfiguration().getBoolean(
345432
"Settings.Performance.VisibilityRateLimit.Enabled");
346433
visibilityRateLimitBucketSize = Math.max(1, getConfiguration().getInt(
@@ -362,4 +449,46 @@ public void loadConfig() {
362449
getServer().getPluginManager().callEvent(new InteractionVisualizerReloadEvent());
363450
}
364451

452+
private boolean configureViewerCulling() {
453+
if (!hideIfObstructed) {
454+
boolean backendChanged = viewerCullingManager.enabled();
455+
viewerCullingManager.shutdown();
456+
viewerCullingManager = ViewerCullingManager.DISABLED;
457+
if (backendChanged && cullingLifecycleInitialized) {
458+
DisplayManager.onCullingBackendChanged();
459+
}
460+
return false;
461+
}
462+
if (viewerCullingManager.enabled()) {
463+
return true;
464+
}
465+
viewerCullingManager.shutdown();
466+
viewerCullingManager = ViewerCullingManager.DISABLED;
467+
if (!isPluginEnabled("CraftEngine")) {
468+
if (!cullingProviderWarningLogged) {
469+
cullingProviderWarningLogged = true;
470+
getLogger().warning("Settings.HideIfViewObstructed requires CraftEngine 26.7+; "
471+
+ "using sent-chunk visibility without occlusion culling.");
472+
}
473+
return false;
474+
}
475+
ViewerCullingManager manager = ViewerCullingManagerLoader.createCraftEngine(
476+
this, DisplayManager::onCullingVisibility).orElse(ViewerCullingManager.DISABLED);
477+
if (!manager.enabled()) {
478+
manager.shutdown();
479+
if (!cullingProviderWarningLogged) {
480+
cullingProviderWarningLogged = true;
481+
getLogger().warning("CraftEngine entity culling and ray tracing must both be enabled "
482+
+ "for Settings.HideIfViewObstructed; using sent-chunk visibility only.");
483+
}
484+
return false;
485+
}
486+
cullingProviderWarningLogged = false;
487+
viewerCullingManager = manager;
488+
if (cullingLifecycleInitialized) {
489+
DisplayManager.onCullingBackendChanged();
490+
}
491+
return true;
492+
}
493+
365494
}

0 commit comments

Comments
 (0)