Skip to content

Commit 49d1837

Browse files
committed
fix: Don't store references to views in weak caches
This was originally done to ensure there was no leaking memory but properly clearing out these caches should be sufficiently sorted by now. Interfaces should keep a view loaded as long as a player is seeing it, and if it sticks around after that that's itself a bug.
1 parent 05c3352 commit 49d1837

File tree

1 file changed

+46
-61
lines changed

1 file changed

+46
-61
lines changed

interfaces/src/main/kotlin/com/noxcrew/interfaces/InterfacesListeners.kt

+46-61
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
7575
Reason.PLUGIN,
7676
Reason.TELEPORT,
7777
Reason.CANT_USE,
78-
Reason.UNLOADED
78+
Reason.UNLOADED,
7979
)
8080

8181
/** An incomplete set of blocks that have some interaction when clicked on. */
@@ -85,7 +85,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
8585
MaterialTags.WOODEN_DOORS,
8686
MaterialTags.WOODEN_TRAPDOORS,
8787
MaterialTags.FENCE_GATES,
88-
MaterialSetTag.BUTTONS
88+
MaterialSetTag.BUTTONS,
8989
)
9090
// Add blocks with inventories
9191
.add(
@@ -100,7 +100,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
100100
Material.LOOM,
101101
Material.CARTOGRAPHY_TABLE,
102102
Material.ENCHANTING_TABLE,
103-
Material.SMITHING_TABLE
103+
Material.SMITHING_TABLE,
104104
)
105105
.add(Material.LEVER)
106106
.add(Material.CAKE)
@@ -109,25 +109,25 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
109109
Material.COPPER_DOOR,
110110
Material.EXPOSED_COPPER_DOOR,
111111
Material.WEATHERED_COPPER_DOOR,
112-
Material.OXIDIZED_COPPER_DOOR
112+
Material.OXIDIZED_COPPER_DOOR,
113113
)
114114
.add(
115115
Material.WAXED_COPPER_DOOR,
116116
Material.WAXED_EXPOSED_COPPER_DOOR,
117117
Material.WAXED_WEATHERED_COPPER_DOOR,
118-
Material.WAXED_OXIDIZED_COPPER_DOOR
118+
Material.WAXED_OXIDIZED_COPPER_DOOR,
119119
)
120120
.add(
121121
Material.COPPER_TRAPDOOR,
122122
Material.EXPOSED_COPPER_TRAPDOOR,
123123
Material.WEATHERED_COPPER_TRAPDOOR,
124-
Material.OXIDIZED_COPPER_TRAPDOOR
124+
Material.OXIDIZED_COPPER_TRAPDOOR,
125125
)
126126
.add(
127127
Material.WAXED_COPPER_TRAPDOOR,
128128
Material.WAXED_EXPOSED_COPPER_TRAPDOOR,
129129
Material.WAXED_WEATHERED_COPPER_TRAPDOOR,
130-
Material.WAXED_OXIDIZED_COPPER_TRAPDOOR
130+
Material.WAXED_OXIDIZED_COPPER_TRAPDOOR,
131131
)
132132
// You can click signs to edit them
133133
.add(MaterialTags.SIGNS)
@@ -138,7 +138,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
138138
val view: InterfaceView,
139139
val onCancel: suspend () -> Unit,
140140
val onComplete: suspend (Component) -> Boolean,
141-
val id: UUID
141+
val id: UUID,
142142
)
143143

144144
/** The view currently being opened. */
@@ -150,19 +150,14 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
150150
.expireAfterWrite(200.toLong(), TimeUnit.MILLISECONDS)
151151
.build()
152152

153-
/** A cache of all ongoing chat queries. */
154-
private val queries: Cache<UUID, ChatQuery> = Caffeine.newBuilder()
155-
.build()
153+
/** A map of all ongoing chat queries. */
154+
private val queries = mutableMapOf<UUID, ChatQuery>()
156155

157-
/** A cache of player interfaces that should be opened again in the future. */
158-
private val backgroundPlayerInterfaceViews: Cache<UUID, PlayerInterfaceView> = Caffeine.newBuilder()
159-
.weakValues()
160-
.build()
156+
/** A map of player interfaces that should be opened again in the future. */
157+
private val backgroundPlayerInterfaceViews = mutableMapOf<UUID, PlayerInterfaceView>()
161158

162-
/** A cache of actively open player interfaces. */
163-
private val openPlayerInterfaceViews: Cache<UUID, PlayerInterfaceView> = Caffeine.newBuilder()
164-
.weakValues()
165-
.build()
159+
/** A map of actively open player interfaces. */
160+
private val openPlayerInterfaceViews = mutableMapOf<UUID, PlayerInterfaceView>()
166161

167162
/** Re-opens the current background interface of [player]. */
168163
public fun reopenInventory(player: Player) {
@@ -182,50 +177,46 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
182177
*/
183178
public fun getBackgroundPlayerInterface(playerId: UUID): PlayerInterfaceView? {
184179
// Check if the menu is definitely still meant to be open
185-
val result = backgroundPlayerInterfaceViews.getIfPresent(playerId) ?: return null
180+
val result = backgroundPlayerInterfaceViews[playerId] ?: return null
186181
if (result.shouldStillBeOpened) return result
187-
backgroundPlayerInterfaceViews.invalidate(playerId)
182+
backgroundPlayerInterfaceViews -= playerId
188183
return null
189184
}
190185

191186
/**
192187
* Returns the currently open player interface for [playerId].
193188
*/
194189
public fun getOpenPlayerInterface(playerId: UUID): PlayerInterfaceView? {
195-
val result = openPlayerInterfaceViews.getIfPresent(playerId) ?: return null
190+
val result = openPlayerInterfaceViews[playerId] ?: return null
196191
if (result.shouldStillBeOpened) return result
197-
openPlayerInterfaceViews.invalidate(playerId)
192+
openPlayerInterfaceViews -= playerId
198193
return null
199194
}
200195

201196
/** Marks the given [view] as the opened player interface. */
202197
public fun openPlayerInterface(playerId: UUID, view: PlayerInterfaceView) {
203-
backgroundPlayerInterfaceViews.invalidate(playerId)
204-
openPlayerInterfaceViews.put(playerId, view)
198+
backgroundPlayerInterfaceViews -= playerId
199+
openPlayerInterfaceViews[playerId] = view
205200
}
206201

207202
/** Sets the background view for [playerId] to [view]. */
208203
public fun setBackgroundView(playerId: UUID, view: PlayerInterfaceView?) {
209204
if (view == null) {
210-
backgroundPlayerInterfaceViews.invalidate(playerId)
205+
backgroundPlayerInterfaceViews -= playerId
211206
} else {
212-
backgroundPlayerInterfaceViews.put(playerId, view)
207+
backgroundPlayerInterfaceViews[playerId] = view
213208
}
214209
}
215210

216211
/** Closes the given [view] of a player interface. */
217212
public fun closePlayerInterface(playerId: UUID, view: PlayerInterfaceView?) {
218213
abortQuery(playerId, view)
219214
if (view == null) {
220-
backgroundPlayerInterfaceViews.invalidate(playerId)
221-
openPlayerInterfaceViews.invalidate(playerId)
215+
backgroundPlayerInterfaceViews -= playerId
216+
openPlayerInterfaceViews -= playerId
222217
} else {
223-
if (backgroundPlayerInterfaceViews.getIfPresent(playerId) === view) {
224-
backgroundPlayerInterfaceViews.invalidate(playerId)
225-
}
226-
if (openPlayerInterfaceViews.getIfPresent(playerId) === view) {
227-
openPlayerInterfaceViews.invalidate(playerId)
228-
}
218+
backgroundPlayerInterfaceViews.remove(playerId, view)
219+
openPlayerInterfaceViews.remove(playerId, view)
229220
}
230221
}
231222

@@ -253,9 +244,8 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
253244

254245
// Move the current open inventory to the background to indicate
255246
// it is no longer the actually opened inventory!
256-
openPlayerInterfaceViews.getIfPresent(event.player.uniqueId)?.also {
247+
openPlayerInterfaceViews.remove(event.player.uniqueId)?.also {
257248
setBackgroundView(event.player.uniqueId, it)
258-
openPlayerInterfaceViews.invalidate(event.player.uniqueId)
259249
}
260250

261251
// Abort any previous query the player had
@@ -314,7 +304,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
314304
!canFreelyMove(
315305
view,
316306
view.backing.relativizePlayerInventorySlot(GridPoint.at(3, event.hotbarButton)),
317-
true
307+
true,
318308
)
319309
) {
320310
event.isCancelled = true
@@ -326,7 +316,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
326316
!canFreelyMove(
327317
view,
328318
view.backing.relativizePlayerInventorySlot(GridPoint.at(4, 4)),
329-
true
319+
true,
330320
)
331321
) {
332322
event.isCancelled = true
@@ -352,7 +342,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
352342
!canFreelyMove(
353343
view,
354344
requireNotNull(GridPoint.fromBukkitChestSlot(index)),
355-
false
345+
false,
356346
)
357347
}
358348
) ||
@@ -363,9 +353,9 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
363353
!canFreelyMove(
364354
view,
365355
view.backing.relativizePlayerInventorySlot(
366-
requireNotNull(GridPoint.fromBukkitPlayerSlot(index))
356+
requireNotNull(GridPoint.fromBukkitPlayerSlot(index)),
367357
),
368-
true
358+
true,
369359
)
370360
}
371361
) {
@@ -398,7 +388,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
398388
} else {
399389
targetSlot
400390
},
401-
isMovingIntoPlayerInventory
391+
isMovingIntoPlayerInventory,
402392
)
403393
) {
404394
event.isCancelled = true
@@ -428,8 +418,6 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
428418

429419
@EventHandler
430420
public fun onPlayerQuit(event: PlayerQuitEvent) {
431-
// Save the contents of their currently shown inventory
432-
abortQuery(event.player.uniqueId, null)
433421
closePlayerInterface(event.player.uniqueId, null)
434422
}
435423

@@ -456,7 +444,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
456444
GridPoint.at(3, player.inventory.heldItemSlot)
457445
} else {
458446
PlayerPane.OFF_HAND_SLOT
459-
}
447+
},
460448
)
461449
val click = convertAction(event.action, player.isSneaking)
462450

@@ -548,8 +536,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
548536
val player = event.player
549537

550538
// Determine if they have a pending query and end it
551-
val query = queries.getIfPresent(player.uniqueId) ?: return
552-
queries.invalidate(player.uniqueId)
539+
val query = queries.remove(player.uniqueId) ?: return
553540

554541
// Complete the query and re-open the view
555542
SCOPE.launch(InterfacesCoroutineDetails(event.player.uniqueId, "completing chat query")) {
@@ -609,7 +596,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
609596
click: ClickType,
610597
slot: Int,
611598
isPlayerInventory: Boolean,
612-
interact: Boolean
599+
interact: Boolean,
613600
): Boolean {
614601
// Determine the type of click, if nothing was clicked we allow it
615602
val raw = view.completedPane?.getRaw(clickedPoint)
@@ -654,7 +641,7 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
654641
Bukkit.getScheduler().runTaskLaterAsynchronously(
655642
plugin,
656643
Runnable { completedClickHandler.cancel() },
657-
120
644+
120,
658645
)
659646
}
660647

@@ -691,15 +678,15 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
691678
view: InterfaceView,
692679
timeout: Duration,
693680
onCancel: suspend () -> Unit,
694-
onComplete: suspend (Component) -> Boolean
681+
onComplete: suspend (Component) -> Boolean,
695682
) {
696683
// Determine if the player has this inventory open
697684
if (!view.isOpen()) return
698685

699686
// Store the current open inventory and remove it from the cache so it does
700687
// not interfere and we can have the player be itemless
701688
val playerId = view.player.uniqueId
702-
openPlayerInterfaceViews.invalidate(playerId)
689+
openPlayerInterfaceViews -= playerId
703690

704691
runSync {
705692
// Close the current inventory to open another to avoid close reasons
@@ -721,36 +708,34 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
721708
view,
722709
onCancel,
723710
onComplete,
724-
id
725-
)
711+
id,
712+
),
726713
)
727714

728715
// Set a timer for to automatically cancel this query to prevent players
729716
// from being stuck in a mode they don't understand for too long
730717
Bukkit.getScheduler().runTaskLater(
731718
plugin,
732719
Runnable {
733-
val queryThen = queries.getIfPresent(playerId) ?: return@Runnable
720+
val queryThen = queries[playerId] ?: return@Runnable
734721
if (queryThen.id != id) return@Runnable
735722

736723
// Remove the query, run the cancel handler, and re-open the view
737-
queries.invalidate(playerId)
724+
queries -= playerId
738725
SCOPE.launch(InterfacesCoroutineDetails(playerId, "cancelling chat query due to timeout")) {
739726
onCancel()
740727
view.reopen()
741728
}
742729
},
743-
timeout.inWholeMilliseconds / 50
730+
timeout.inWholeMilliseconds / 50,
744731
)
745732
}
746733

747734
/** Aborts an ongoing query for [playerId], without re-opening the original view. */
748735
public fun abortQuery(playerId: UUID, view: InterfaceView?) {
749-
val query = queries.getIfPresent(playerId) ?: return
750-
751736
// Only end the specific view on request
752-
if (view != null && query.view != view) return
753-
queries.invalidate(playerId)
737+
if (view != null && queries[playerId]?.view != view) return
738+
val query = queries.remove(playerId) ?: return
754739

755740
SCOPE.launch(InterfacesCoroutineDetails(playerId, "aborting chat query")) {
756741
// Run the cancellation handler

0 commit comments

Comments
 (0)