Class PlayerCacheManager
Three field shapes are recognised: a Map keyed by UUID (the entire entry --
including a nested value, e.g. Map<UUID, Map<String, Long>> -- is removed on quit), a
Set<UUID> (the UUID itself is removed), and a Map whose VALUE is a
UUID (every entry whose value equals the quitting player's UUID is removed, other
players' entries are left intact). A field whose generic shape cannot be resolved to one of
these three is refused at registration time -- an annotation that cannot take effect fails
loudly rather than being silently skipped.
A non-bean instance -- one that is never resolved from any SimpleContainer, e.g. a
command validator new-ed directly in a constructor -- can register itself via tryRegister(Object) even before the core plugin is enabled; see that method's javadoc.
- Since:
- 6.2.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceOpt-in contract for a tracked instance that also carries time-based (as opposed to player-quit-based) state to prune.private static enumThe three field shapesregisterBean(Object)can sweep.private static classprivate static class -
Field Summary
FieldsModifier and TypeFieldDescription(package private) static final longPeriod, in ticks, between two consecutive expiry sweeps (sweepExpiredEntries()).private static final Loggerprivate final List<PlayerCacheManager.TrackedBean> -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static PlayerCacheManager.FieldShapeclassifyField(Field field) Classifies aPlayerCache-annotated field's generic shape.intReturns the number of beans being tracked.private booleanprivate static booleanvoidonPlayerQuit(UUID playerId) Called when a player quits.voidregisterBean(Object bean) Scans a bean for @PlayerCache fields and tracks them.private static PlayerCacheManagervoidPeriodic, time-based expiry sweep -- driven by the framework's ownScheduled(i.e.private static voidsweepField(Object value, PlayerCacheManager.FieldShape shape, UUID playerId) static voidtryRegister(Object instance) Attempts lazy first-use registration of a non-bean instance with the livePlayerCacheManagersingleton.static voidtryUnregister(Object instance) The static counterpart totryRegister(Object)-- unregisters a previously registered non-bean instance from the livePlayerCacheManagersingleton.voidunregisterBean(Object bean) Removes a bean from tracking.
-
Field Details
-
LOGGER
-
EXPIRY_SWEEP_PERIOD_TICKS
static final long EXPIRY_SWEEP_PERIOD_TICKSPeriod, in ticks, between two consecutive expiry sweeps (sweepExpiredEntries()).Chosen conservatively: this task runs for the entire lifetime of a long-running server, and the per-player state it exists to bound (command cooldowns, usage locks) is expressed in seconds-to-minutes, not milliseconds -- there is no correctness requirement for the sweep to run more often than once every few minutes. 6000 ticks is 5 minutes at the server's nominal 20 ticks/second, which keeps the sweep's own overhead negligible (an empty or small registry costs microseconds per pass) while bounding the worst-case staleness of any expired entry to 5 minutes.
- See Also:
-
trackedBeans
-
-
Constructor Details
-
PlayerCacheManager
public PlayerCacheManager()
-
-
Method Details
-
tryRegister
Attempts lazy first-use registration of a non-bean instance with the livePlayerCacheManagersingleton.Accepts any object -- it does not need to be a container-managed bean; this is the entry point objects that are
new-ed directly (e.g. a command validator constructed insideBaseCommandExecutor's constructor) use to reach the sweep mechanism, sincePluginManager's bean-iteration registration loops only ever see objects resolved from a plugin'sSimpleContainer.Safe to call before the core plugin is enabled. If
UltiTools.getInstance()(or itsPluginManager/PlayerCacheManager) is not yet available -- for example, a barenew MyCommand()constructed in a unit test with no running server -- this method returns without throwing and without registering anything. It is intended to be retried on a later first-use once the core plugin is available; degrading gracefully to "never registered, never swept" in a pure-unit-test context is deliberate (D-03).Idempotent per instance. Calling this method more than once with the same instance (by reference identity, not
equals) results in exactly one tracked entry and one sweep per player quit -- seeregisterBean(Object).The caller owns unregistration. This method has no matching automatic teardown; a caller that registers itself is responsible for calling
tryUnregister(Object)on its own teardown, so the manager does not keep tracking an instance whose owner has gone away.- Parameters:
instance- the object to register; scanned forPlayerCache-annotated fields- Since:
- 6.3.0
-
tryUnregister
The static counterpart totryRegister(Object)-- unregisters a previously registered non-bean instance from the livePlayerCacheManagersingleton.Safe to call before the core plugin is enabled or after it has been disabled: if the live manager cannot be resolved, this is a no-op. Also safe to call on an instance that was never registered (e.g. because an earlier
tryRegister(Object)attempt found the core plugin unavailable) --unregisterBean(Object)is itself a no-op in that case.- Parameters:
instance- the object to stop tracking- Since:
- 6.3.0
-
resolveLive
-
registerBean
Scans a bean for @PlayerCache fields and tracks them.Idempotent by reference identity: registering the same instance more than once is a no-op on the second and subsequent calls, so an instance never accumulates duplicate tracked entries or is swept more than once per player quit.
- Throws:
PluginModuleException- if aPlayerCache-annotated field's shape is none of the three this manager can sweep (aMapkeyed byUUID, aSet<UUID>, or aMapwhose value is aUUID) -- an annotation that cannot take effect must fail loudly rather than be silently skipped.
-
isTracked
-
classifyField
Classifies aPlayerCache-annotated field's generic shape.Java 8 erasure means the key-side and value-side
Mapcases cannot be told apart fromField.getType()alone -- this readsField.getGenericType()and its actual type arguments instead. Where the generic signature is absent or unresolvable (a raw type, or neither side isUUID), this returnsnullrather than guessing, and the caller refuses the field.- Parameters:
field- the field to classify- Returns:
- the recognised shape, or
nullif the field's shape is unsupported
-
isUuid
-
unregisterBean
Removes a bean from tracking. -
onPlayerQuit
Called when a player quits. Cleans up all tracked fields across all three supported shapes. -
sweepField
-
getTrackedBeanCount
public int getTrackedBeanCount()Returns the number of beans being tracked. -
sweepExpiredEntries
public void sweepExpiredEntries()Periodic, time-based expiry sweep -- driven by the framework's ownScheduled(i.e.@Scheduled) mechanism rather than a hand-rolledBukkitRunnable, because this state is stale on a clock, not on a player-quit event: it must be pruned whether or not any player is online (D-03 -- mirrorsCooldownValidator.cleanupExpired's shape).Every tracked instance that implements
PlayerCacheManager.ExpiringPlayerCachehasPlayerCacheManager.ExpiringPlayerCache.sweepExpired()invoked once per pass. One participant's hook throwing is caught, logged, and does not prevent the remaining participants' hooks from running in the same pass. An instance that does not implementPlayerCacheManager.ExpiringPlayerCacheis not visited at all -- opt-in by type, not by reflection guessing at method names. Tolerates an empty registry and being invoked mid-shutdown: it touches only this manager's own tracked list, never the Bukkit API directly.Sweep period: see
EXPIRY_SWEEP_PERIOD_TICKS(5 minutes -- see that constant's javadoc for the rationale).- Since:
- 6.3.0
-