From 43eaa6916b495f8a3a351eeef55357ec15ec64f9 Mon Sep 17 00:00:00 2001 From: Kris Date: Fri, 13 Sep 2024 18:00:20 +0300 Subject: [PATCH 1/3] fix: avoid pooling info objects which get destroyed due to errors Fixes https://github.com/blurite/rsprot/issues/24 --- .../protocol/game/outgoing/info/npcinfo/NpcInfo.kt | 3 +++ .../game/outgoing/info/npcinfo/NpcInfoProtocol.kt | 4 ++++ .../game/outgoing/info/playerinfo/PlayerInfo.kt | 3 +++ .../outgoing/info/playerinfo/PlayerInfoProtocol.kt | 4 ++++ .../game/outgoing/info/util/ReferencePooledObject.kt | 10 ++++++++++ .../protocol/game/outgoing/info/npcinfo/NpcInfo.kt | 3 +++ .../game/outgoing/info/npcinfo/NpcInfoProtocol.kt | 4 ++++ .../game/outgoing/info/playerinfo/PlayerInfo.kt | 3 +++ .../outgoing/info/playerinfo/PlayerInfoProtocol.kt | 4 ++++ .../game/outgoing/info/util/ReferencePooledObject.kt | 10 ++++++++++ .../outgoing/info/worldentityinfo/WorldEntityInfo.kt | 4 ++++ .../info/worldentityinfo/WorldEntityProtocol.kt | 4 ++++ .../protocol/game/outgoing/info/npcinfo/NpcInfo.kt | 3 +++ .../game/outgoing/info/npcinfo/NpcInfoProtocol.kt | 4 ++++ .../game/outgoing/info/playerinfo/PlayerInfo.kt | 3 +++ .../outgoing/info/playerinfo/PlayerInfoProtocol.kt | 4 ++++ .../game/outgoing/info/util/ReferencePooledObject.kt | 10 ++++++++++ .../outgoing/info/worldentityinfo/WorldEntityInfo.kt | 4 ++++ .../info/worldentityinfo/WorldEntityProtocol.kt | 4 ++++ .../protocol/game/outgoing/info/npcinfo/NpcInfo.kt | 3 +++ .../game/outgoing/info/npcinfo/NpcInfoProtocol.kt | 4 ++++ .../game/outgoing/info/playerinfo/PlayerInfo.kt | 3 +++ .../outgoing/info/playerinfo/PlayerInfoProtocol.kt | 4 ++++ .../game/outgoing/info/util/ReferencePooledObject.kt | 10 ++++++++++ .../outgoing/info/worldentityinfo/WorldEntityInfo.kt | 4 ++++ .../info/worldentityinfo/WorldEntityProtocol.kt | 4 ++++ .../protocol/game/outgoing/info/npcinfo/NpcInfo.kt | 3 +++ .../game/outgoing/info/npcinfo/NpcInfoProtocol.kt | 4 ++++ .../game/outgoing/info/playerinfo/PlayerInfo.kt | 3 +++ .../outgoing/info/playerinfo/PlayerInfoProtocol.kt | 4 ++++ .../game/outgoing/info/util/ReferencePooledObject.kt | 10 ++++++++++ .../outgoing/info/worldentityinfo/WorldEntityInfo.kt | 4 ++++ .../info/worldentityinfo/WorldEntityProtocol.kt | 4 ++++ 33 files changed, 152 insertions(+) diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt index 96ad6589a..7a8ce2bf6 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt @@ -135,6 +135,7 @@ public class NpcInfo internal constructor( * This exception will be propagated further during the [toNpcInfoPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -146,6 +147,8 @@ public class NpcInfo internal constructor( */ private var builtIntoPacket: Boolean = false + override fun isDestroyed(): Boolean = this.exception != null + /** * Returns the backing byte buffer holding all the computed information. * @throws IllegalStateException if the buffer is null, meaning it has no yet been diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt index 1144aa4fe..e07e4d470 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt @@ -75,6 +75,10 @@ public class NpcInfoProtocol( * @param info the npc info object to deallocate */ public fun dealloc(info: NpcInfo) { + // Prevent returning a destroyed npc info object back into the pool + if (info.isDestroyed()) { + return + } npcInfoRepository.dealloc(info.localPlayerIndex) } diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 53dd7fca1..9aff44f84 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -150,6 +150,7 @@ public class PlayerInfo internal constructor( * This exception will be propagated further during the [toPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -174,6 +175,8 @@ public class PlayerInfo internal constructor( @Throws(IllegalStateException::class) public fun backingBuffer(): ByteBuf = checkNotNull(buffer) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the build area of this player info object. * This will ensure that no players outside of this box will be diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index a3f5a5d04..246a5b7ed 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -101,6 +101,10 @@ public class PlayerInfoProtocol( * @param info the player info object */ public fun dealloc(info: PlayerInfo) { + // Prevent returning a destroyed player info object back into the pool + if (info.isDestroyed()) { + return + } playerInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt index d35b1f531..0cea0ae3e 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt @@ -30,4 +30,14 @@ public interface ReferencePooledObject { * during the [onAlloc], to ensure no work is 'wasted'. */ public fun onDealloc() + + /** + * Whether this reference pooled object is destroyed. + * A destroyed object will not be returned back to the pool and instead will be left off for the + * garbage collector to clean up in due time. This condition is only hit when there was some error + * thrown during the processing of a given info object. In order to mitigate potential future + * problems that might continue to stem from re-using this object, we discard it altogether. + * @return whether the info object is destroyed and should not be returned to the pool. + */ + public fun isDestroyed(): Boolean } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt index 17ac445aa..3b16c3a42 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt @@ -58,6 +58,7 @@ public class NpcInfo internal constructor( * This exception will be propagated further during the [toNpcInfoPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -66,6 +67,8 @@ public class NpcInfo internal constructor( */ internal val details: Array = arrayOfNulls(WORLD_ENTITY_CAPACITY + 1) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the build area of a given world to the specified one. * This will ensure that no NPCs outside of this box will be diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt index b3c63f922..cb15ab1e0 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt @@ -78,6 +78,10 @@ public class NpcInfoProtocol( * @param info the npc info object to deallocate */ public fun dealloc(info: NpcInfo) { + // Prevent returning a destroyed npc info object back into the pool + if (info.isDestroyed()) { + return + } npcInfoRepository.dealloc(info.localPlayerIndex) } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index bdcc5b8ea..ab33e1628 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -83,6 +83,7 @@ public class PlayerInfo internal constructor( * This exception will be propagated further during the [toPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -118,6 +119,8 @@ public class PlayerInfo internal constructor( avatar.extendedInfo.invalidateAppearanceCache() } + override fun isDestroyed(): Boolean = this.exception != null + /** * Sets an active world in which the player currently resides. * @param worldId the world id in which the player resides. A value of -1 implies diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 3fdb927c6..58d4069d4 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -107,6 +107,10 @@ public class PlayerInfoProtocol( * @param info the player info object */ public fun dealloc(info: PlayerInfo) { + // Prevent returning a destroyed player info object back into the pool + if (info.isDestroyed()) { + return + } playerInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt index 01cc120f8..99576baac 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt @@ -31,4 +31,14 @@ public interface ReferencePooledObject { * during the [onAlloc], to ensure no work is 'wasted'. */ public fun onDealloc() + + /** + * Whether this reference pooled object is destroyed. + * A destroyed object will not be returned back to the pool and instead will be left off for the + * garbage collector to clean up in due time. This condition is only hit when there was some error + * thrown during the processing of a given info object. In order to mitigate potential future + * problems that might continue to stem from re-using this object, we discard it altogether. + * @return whether the info object is destroyed and should not be returned to the pool. + */ + public fun isDestroyed(): Boolean } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index 78370ea5b..fad40b0c7 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -82,10 +82,14 @@ public class WorldEntityInfo internal constructor( private val addedWorldEntities = ArrayList() private val removedWorldEntities = ArrayList() private var buffer: ByteBuf? = null + + @Volatile internal var exception: Exception? = null private var builtIntoPacket: Boolean = false private var renderCoord: CoordGrid = CoordGrid.INVALID + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the render distance for this player, potentially allowing * the world entities to be rendered from further away. All instances diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt index 86dfaeb07..1e2cd37cf 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt @@ -63,6 +63,10 @@ public class WorldEntityProtocol( * @param info the world entity info to be deallocated. */ public fun dealloc(info: WorldEntityInfo) { + // Prevent returning a destroyed worldentity info object back into the pool + if (info.isDestroyed()) { + return + } worldEntityInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt index 2a3006b1d..2e09dba04 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt @@ -58,6 +58,7 @@ public class NpcInfo internal constructor( * This exception will be propagated further during the [toNpcInfoPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -66,6 +67,8 @@ public class NpcInfo internal constructor( */ internal val details: Array = arrayOfNulls(WORLD_ENTITY_CAPACITY + 1) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the build area of a given world to the specified one. * This will ensure that no NPCs outside of this box will be diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt index b3c63f922..cb15ab1e0 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt @@ -78,6 +78,10 @@ public class NpcInfoProtocol( * @param info the npc info object to deallocate */ public fun dealloc(info: NpcInfo) { + // Prevent returning a destroyed npc info object back into the pool + if (info.isDestroyed()) { + return + } npcInfoRepository.dealloc(info.localPlayerIndex) } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index bdcc5b8ea..b228ed671 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -83,6 +83,7 @@ public class PlayerInfo internal constructor( * This exception will be propagated further during the [toPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -107,6 +108,8 @@ public class PlayerInfo internal constructor( */ private var invalidateAppearanceCache: Boolean = false + override fun isDestroyed(): Boolean = this.exception != null + /** * Checks if appearance needs invalidation, and invalidates if so. */ diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 3fdb927c6..58d4069d4 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -107,6 +107,10 @@ public class PlayerInfoProtocol( * @param info the player info object */ public fun dealloc(info: PlayerInfo) { + // Prevent returning a destroyed player info object back into the pool + if (info.isDestroyed()) { + return + } playerInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt index 01cc120f8..99576baac 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt @@ -31,4 +31,14 @@ public interface ReferencePooledObject { * during the [onAlloc], to ensure no work is 'wasted'. */ public fun onDealloc() + + /** + * Whether this reference pooled object is destroyed. + * A destroyed object will not be returned back to the pool and instead will be left off for the + * garbage collector to clean up in due time. This condition is only hit when there was some error + * thrown during the processing of a given info object. In order to mitigate potential future + * problems that might continue to stem from re-using this object, we discard it altogether. + * @return whether the info object is destroyed and should not be returned to the pool. + */ + public fun isDestroyed(): Boolean } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index 78370ea5b..fad40b0c7 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -82,10 +82,14 @@ public class WorldEntityInfo internal constructor( private val addedWorldEntities = ArrayList() private val removedWorldEntities = ArrayList() private var buffer: ByteBuf? = null + + @Volatile internal var exception: Exception? = null private var builtIntoPacket: Boolean = false private var renderCoord: CoordGrid = CoordGrid.INVALID + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the render distance for this player, potentially allowing * the world entities to be rendered from further away. All instances diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt index 86dfaeb07..1e2cd37cf 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt @@ -63,6 +63,10 @@ public class WorldEntityProtocol( * @param info the world entity info to be deallocated. */ public fun dealloc(info: WorldEntityInfo) { + // Prevent returning a destroyed worldentity info object back into the pool + if (info.isDestroyed()) { + return + } worldEntityInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt index 2a3006b1d..2e09dba04 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt @@ -58,6 +58,7 @@ public class NpcInfo internal constructor( * This exception will be propagated further during the [toNpcInfoPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -66,6 +67,8 @@ public class NpcInfo internal constructor( */ internal val details: Array = arrayOfNulls(WORLD_ENTITY_CAPACITY + 1) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the build area of a given world to the specified one. * This will ensure that no NPCs outside of this box will be diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt index b3c63f922..cb15ab1e0 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt @@ -78,6 +78,10 @@ public class NpcInfoProtocol( * @param info the npc info object to deallocate */ public fun dealloc(info: NpcInfo) { + // Prevent returning a destroyed npc info object back into the pool + if (info.isDestroyed()) { + return + } npcInfoRepository.dealloc(info.localPlayerIndex) } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 6b55b32aa..519580352 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -83,6 +83,7 @@ public class PlayerInfo internal constructor( * This exception will be propagated further during the [toPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -107,6 +108,8 @@ public class PlayerInfo internal constructor( */ private var invalidateAppearanceCache: Boolean = false + override fun isDestroyed(): Boolean = this.exception != null + /** * Checks if appearance needs invalidation, and invalidates if so. */ diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 3fdb927c6..58d4069d4 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -107,6 +107,10 @@ public class PlayerInfoProtocol( * @param info the player info object */ public fun dealloc(info: PlayerInfo) { + // Prevent returning a destroyed player info object back into the pool + if (info.isDestroyed()) { + return + } playerInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt index 01cc120f8..99576baac 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt @@ -31,4 +31,14 @@ public interface ReferencePooledObject { * during the [onAlloc], to ensure no work is 'wasted'. */ public fun onDealloc() + + /** + * Whether this reference pooled object is destroyed. + * A destroyed object will not be returned back to the pool and instead will be left off for the + * garbage collector to clean up in due time. This condition is only hit when there was some error + * thrown during the processing of a given info object. In order to mitigate potential future + * problems that might continue to stem from re-using this object, we discard it altogether. + * @return whether the info object is destroyed and should not be returned to the pool. + */ + public fun isDestroyed(): Boolean } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index 78370ea5b..fad40b0c7 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -82,10 +82,14 @@ public class WorldEntityInfo internal constructor( private val addedWorldEntities = ArrayList() private val removedWorldEntities = ArrayList() private var buffer: ByteBuf? = null + + @Volatile internal var exception: Exception? = null private var builtIntoPacket: Boolean = false private var renderCoord: CoordGrid = CoordGrid.INVALID + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the render distance for this player, potentially allowing * the world entities to be rendered from further away. All instances diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt index 86dfaeb07..1e2cd37cf 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt @@ -63,6 +63,10 @@ public class WorldEntityProtocol( * @param info the world entity info to be deallocated. */ public fun dealloc(info: WorldEntityInfo) { + // Prevent returning a destroyed worldentity info object back into the pool + if (info.isDestroyed()) { + return + } worldEntityInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt index 2a3006b1d..2e09dba04 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfo.kt @@ -58,6 +58,7 @@ public class NpcInfo internal constructor( * This exception will be propagated further during the [toNpcInfoPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -66,6 +67,8 @@ public class NpcInfo internal constructor( */ internal val details: Array = arrayOfNulls(WORLD_ENTITY_CAPACITY + 1) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the build area of a given world to the specified one. * This will ensure that no NPCs outside of this box will be diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt index b3c63f922..cb15ab1e0 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/npcinfo/NpcInfoProtocol.kt @@ -78,6 +78,10 @@ public class NpcInfoProtocol( * @param info the npc info object to deallocate */ public fun dealloc(info: NpcInfo) { + // Prevent returning a destroyed npc info object back into the pool + if (info.isDestroyed()) { + return + } npcInfoRepository.dealloc(info.localPlayerIndex) } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 7c782f25f..3cfa74b2b 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -150,6 +150,7 @@ public class PlayerInfo internal constructor( * This exception will be propagated further during the [toPacket] function call, * allowing the server to handle it properly at a per-player basis. */ + @Volatile internal var exception: Exception? = null /** @@ -174,6 +175,8 @@ public class PlayerInfo internal constructor( @Throws(IllegalStateException::class) public fun backingBuffer(): ByteBuf = checkNotNull(buffer) + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the render coordinate for the provided world id. * This coordinate is what will be used to perform distance checks between the player diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index a3f5a5d04..246a5b7ed 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -101,6 +101,10 @@ public class PlayerInfoProtocol( * @param info the player info object */ public fun dealloc(info: PlayerInfo) { + // Prevent returning a destroyed player info object back into the pool + if (info.isDestroyed()) { + return + } playerInfoRepository.dealloc(info.localIndex) } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt index 01cc120f8..99576baac 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/util/ReferencePooledObject.kt @@ -31,4 +31,14 @@ public interface ReferencePooledObject { * during the [onAlloc], to ensure no work is 'wasted'. */ public fun onDealloc() + + /** + * Whether this reference pooled object is destroyed. + * A destroyed object will not be returned back to the pool and instead will be left off for the + * garbage collector to clean up in due time. This condition is only hit when there was some error + * thrown during the processing of a given info object. In order to mitigate potential future + * problems that might continue to stem from re-using this object, we discard it altogether. + * @return whether the info object is destroyed and should not be returned to the pool. + */ + public fun isDestroyed(): Boolean } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index 78370ea5b..fad40b0c7 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -82,10 +82,14 @@ public class WorldEntityInfo internal constructor( private val addedWorldEntities = ArrayList() private val removedWorldEntities = ArrayList() private var buffer: ByteBuf? = null + + @Volatile internal var exception: Exception? = null private var builtIntoPacket: Boolean = false private var renderCoord: CoordGrid = CoordGrid.INVALID + override fun isDestroyed(): Boolean = this.exception != null + /** * Updates the render distance for this player, potentially allowing * the world entities to be rendered from further away. All instances diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt index 86dfaeb07..1e2cd37cf 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityProtocol.kt @@ -63,6 +63,10 @@ public class WorldEntityProtocol( * @param info the world entity info to be deallocated. */ public fun dealloc(info: WorldEntityInfo) { + // Prevent returning a destroyed worldentity info object back into the pool + if (info.isDestroyed()) { + return + } worldEntityInfoRepository.dealloc(info.localIndex) } From 5180bd96dd155ab2ff28f843b9c6ed5023b93f60 Mon Sep 17 00:00:00 2001 From: Kris Date: Fri, 13 Sep 2024 19:41:30 +0300 Subject: [PATCH 2/3] fix: issue with reallocating player info on same cycle This fixes a potential issue where servers might end up deallocating and reallocating the same index player info object on the same cycle. While typically fine, if the previous player and the new player are in the same rough area, any observers of them might think the player shouldn't receive any updates. This could result in visual glitching. --- .../outgoing/info/playerinfo/PlayerAvatar.kt | 24 +++++++++++++++++++ .../outgoing/info/playerinfo/PlayerInfo.kt | 7 ++++++ .../info/playerinfo/PlayerInfoProtocol.kt | 14 +++++++++++ .../outgoing/info/playerinfo/PlayerAvatar.kt | 24 +++++++++++++++++++ .../outgoing/info/playerinfo/PlayerInfo.kt | 7 ++++++ .../info/playerinfo/PlayerInfoProtocol.kt | 14 +++++++++++ .../outgoing/info/playerinfo/PlayerAvatar.kt | 24 +++++++++++++++++++ .../outgoing/info/playerinfo/PlayerInfo.kt | 7 ++++++ .../info/playerinfo/PlayerInfoProtocol.kt | 14 +++++++++++ .../outgoing/info/playerinfo/PlayerAvatar.kt | 24 +++++++++++++++++++ .../outgoing/info/playerinfo/PlayerInfo.kt | 9 ++++++- .../info/playerinfo/PlayerInfoProtocol.kt | 14 +++++++++++ .../outgoing/info/playerinfo/PlayerAvatar.kt | 24 +++++++++++++++++++ .../outgoing/info/playerinfo/PlayerInfo.kt | 7 ++++++ .../info/playerinfo/PlayerInfoProtocol.kt | 14 +++++++++++ 15 files changed, 226 insertions(+), 1 deletion(-) diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt index e0e3f0df5..a918f768b 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt @@ -80,8 +80,32 @@ public class PlayerAvatar internal constructor( huffmanCodec, ) + /** + * Whether this avatar is completed hidden from everyone else. Note that this completely skips + * sending any information to the client about this given avatar, it is not the same as soft + * hiding via the appearance extended info. + */ internal var hidden: Boolean = false + /** + * The [PlayerInfoProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a player, + * which can happen when a player is deallocated and reallocated on the same cycle, + * which could result in other players not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = PlayerInfoProtocol.cycleCount + + /** + * Sets the avatar as hidden (or unhidden, depending on [hidden]). When hidden via this function, + * no information is transmitted to the clients about this avatar. It is a hard-hiding function, + * unlike the one via appearance extended info, which strictly only hides client-side, but all + * clients still receive information about the client existing. + * The benefit to this function is that no plugins or RuneLite implementations can snoop on other + * players that are meant to be hidden. The downside, however, is that because the client has no + * knowledge of that specific avatar whatsoever, un-hiding while the player is moving is not as + * smooth as with the appearance variant, since it first appears as if the player teleported in. + */ public fun setHidden(hidden: Boolean) { this.hidden = hidden } diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 9aff44f84..8249a4343 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -613,6 +613,12 @@ public class PlayerInfo internal constructor( if (other.avatar.hidden) { return false } + // If the avatar was allocated on this cycle, ensure we remove (and potentially re-add later) + // this avatar. This is due to someone logging out and another player taking the avatar the same + // cycle - which would otherwise potentially go by unnoticed, with the client assuming nothing changed. + if (other.avatar.allocateCycle == PlayerInfoProtocol.cycleCount) { + return false + } val coord = other.avatar.currentCoord if (!coord.inDistance(this.avatar.currentCoord, this.avatar.resizeRange)) { return false @@ -711,6 +717,7 @@ public class PlayerInfo internal constructor( this.oldSchoolClientType = oldSchoolClientType this.buildArea = BuildArea.INVALID avatar.reset() + this.avatar.allocateCycle = PlayerInfoProtocol.cycleCount lowResolutionIndices.fill(0) lowResolutionCount = 0 highResolutionIndices.fill(0) diff --git a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 246a5b7ed..7aba114b7 100644 --- a/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-221/osrs-221-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -122,6 +122,7 @@ public class PlayerInfoProtocol( prepareExtendedInfo() putExtendedInfo() postUpdate() + cycleCount++ } /** @@ -258,5 +259,18 @@ public class PlayerInfoProtocol( */ public const val PROTOCOL_CAPACITY: Int = 2048 private val logger: InlineLogger = InlineLogger() + + /** + * The number of Player info update cycles that have occurred. + * We need to track this to avoid a nasty bug with servers de-allocating + re-allocating + * an avatar on the same cycle, in a small area. The effective bug is that another player takes + * ones' avatar, which leads to info protocol thinking nothing has changed (assuming the new player + * is still within range of the old one, enough to be in high resolution). + * + * We solve this by forcibly removing a player from high resolution view if the avatar was + * allocated on current cycle. If the player is still within range, they will be re-added + * later on in the cycle via low resolution updates - but correctly this time around! + */ + internal var cycleCount: Int = 0 } } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt index e0e3f0df5..a918f768b 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt @@ -80,8 +80,32 @@ public class PlayerAvatar internal constructor( huffmanCodec, ) + /** + * Whether this avatar is completed hidden from everyone else. Note that this completely skips + * sending any information to the client about this given avatar, it is not the same as soft + * hiding via the appearance extended info. + */ internal var hidden: Boolean = false + /** + * The [PlayerInfoProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a player, + * which can happen when a player is deallocated and reallocated on the same cycle, + * which could result in other players not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = PlayerInfoProtocol.cycleCount + + /** + * Sets the avatar as hidden (or unhidden, depending on [hidden]). When hidden via this function, + * no information is transmitted to the clients about this avatar. It is a hard-hiding function, + * unlike the one via appearance extended info, which strictly only hides client-side, but all + * clients still receive information about the client existing. + * The benefit to this function is that no plugins or RuneLite implementations can snoop on other + * players that are meant to be hidden. The downside, however, is that because the client has no + * knowledge of that specific avatar whatsoever, un-hiding while the player is moving is not as + * smooth as with the appearance variant, since it first appears as if the player teleported in. + */ public fun setHidden(hidden: Boolean) { this.hidden = hidden } diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index ab33e1628..57be88a86 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -685,6 +685,12 @@ public class PlayerInfo internal constructor( if (other.avatar.hidden) { return false } + // If the avatar was allocated on this cycle, ensure we remove (and potentially re-add later) + // this avatar. This is due to someone logging out and another player taking the avatar the same + // cycle - which would otherwise potentially go by unnoticed, with the client assuming nothing changed. + if (other.avatar.allocateCycle == PlayerInfoProtocol.cycleCount) { + return false + } val coord = other.avatar.currentCoord if (!details.renderCoord.inDistance(coord, this.avatar.resizeRange)) { return false @@ -801,6 +807,7 @@ public class PlayerInfo internal constructor( avatar.extendedInfo.localIndex = index this.oldSchoolClientType = oldSchoolClientType avatar.reset() + this.avatar.allocateCycle = PlayerInfoProtocol.cycleCount this.activeWorldId = ROOT_WORLD // There is always a root world! val rootDetails = protocol.detailsStorage.poll(ROOT_WORLD) diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 58d4069d4..17e8ba9ee 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -128,6 +128,7 @@ public class PlayerInfoProtocol( prepareExtendedInfo() putExtendedInfo() postUpdate() + cycleCount++ } /** @@ -281,5 +282,18 @@ public class PlayerInfoProtocol( */ public const val PROTOCOL_CAPACITY: Int = 2048 private val logger: InlineLogger = InlineLogger() + + /** + * The number of Player info update cycles that have occurred. + * We need to track this to avoid a nasty bug with servers de-allocating + re-allocating + * an avatar on the same cycle, in a small area. The effective bug is that another player takes + * ones' avatar, which leads to info protocol thinking nothing has changed (assuming the new player + * is still within range of the old one, enough to be in high resolution). + * + * We solve this by forcibly removing a player from high resolution view if the avatar was + * allocated on current cycle. If the player is still within range, they will be re-added + * later on in the cycle via low resolution updates - but correctly this time around! + */ + internal var cycleCount: Int = 0 } } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt index e0e3f0df5..a918f768b 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt @@ -80,8 +80,32 @@ public class PlayerAvatar internal constructor( huffmanCodec, ) + /** + * Whether this avatar is completed hidden from everyone else. Note that this completely skips + * sending any information to the client about this given avatar, it is not the same as soft + * hiding via the appearance extended info. + */ internal var hidden: Boolean = false + /** + * The [PlayerInfoProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a player, + * which can happen when a player is deallocated and reallocated on the same cycle, + * which could result in other players not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = PlayerInfoProtocol.cycleCount + + /** + * Sets the avatar as hidden (or unhidden, depending on [hidden]). When hidden via this function, + * no information is transmitted to the clients about this avatar. It is a hard-hiding function, + * unlike the one via appearance extended info, which strictly only hides client-side, but all + * clients still receive information about the client existing. + * The benefit to this function is that no plugins or RuneLite implementations can snoop on other + * players that are meant to be hidden. The downside, however, is that because the client has no + * knowledge of that specific avatar whatsoever, un-hiding while the player is moving is not as + * smooth as with the appearance variant, since it first appears as if the player teleported in. + */ public fun setHidden(hidden: Boolean) { this.hidden = hidden } diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index b228ed671..46b5029a6 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -685,6 +685,12 @@ public class PlayerInfo internal constructor( if (other.avatar.hidden) { return false } + // If the avatar was allocated on this cycle, ensure we remove (and potentially re-add later) + // this avatar. This is due to someone logging out and another player taking the avatar the same + // cycle - which would otherwise potentially go by unnoticed, with the client assuming nothing changed. + if (other.avatar.allocateCycle == PlayerInfoProtocol.cycleCount) { + return false + } val coord = other.avatar.currentCoord if (!details.renderCoord.inDistance(coord, this.avatar.resizeRange)) { return false @@ -801,6 +807,7 @@ public class PlayerInfo internal constructor( avatar.extendedInfo.localIndex = index this.oldSchoolClientType = oldSchoolClientType avatar.reset() + this.avatar.allocateCycle = PlayerInfoProtocol.cycleCount this.activeWorldId = ROOT_WORLD // There is always a root world! val rootDetails = protocol.detailsStorage.poll(ROOT_WORLD) diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 58d4069d4..17e8ba9ee 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -128,6 +128,7 @@ public class PlayerInfoProtocol( prepareExtendedInfo() putExtendedInfo() postUpdate() + cycleCount++ } /** @@ -281,5 +282,18 @@ public class PlayerInfoProtocol( */ public const val PROTOCOL_CAPACITY: Int = 2048 private val logger: InlineLogger = InlineLogger() + + /** + * The number of Player info update cycles that have occurred. + * We need to track this to avoid a nasty bug with servers de-allocating + re-allocating + * an avatar on the same cycle, in a small area. The effective bug is that another player takes + * ones' avatar, which leads to info protocol thinking nothing has changed (assuming the new player + * is still within range of the old one, enough to be in high resolution). + * + * We solve this by forcibly removing a player from high resolution view if the avatar was + * allocated on current cycle. If the player is still within range, they will be re-added + * later on in the cycle via low resolution updates - but correctly this time around! + */ + internal var cycleCount: Int = 0 } } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt index e0e3f0df5..a918f768b 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt @@ -80,8 +80,32 @@ public class PlayerAvatar internal constructor( huffmanCodec, ) + /** + * Whether this avatar is completed hidden from everyone else. Note that this completely skips + * sending any information to the client about this given avatar, it is not the same as soft + * hiding via the appearance extended info. + */ internal var hidden: Boolean = false + /** + * The [PlayerInfoProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a player, + * which can happen when a player is deallocated and reallocated on the same cycle, + * which could result in other players not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = PlayerInfoProtocol.cycleCount + + /** + * Sets the avatar as hidden (or unhidden, depending on [hidden]). When hidden via this function, + * no information is transmitted to the clients about this avatar. It is a hard-hiding function, + * unlike the one via appearance extended info, which strictly only hides client-side, but all + * clients still receive information about the client existing. + * The benefit to this function is that no plugins or RuneLite implementations can snoop on other + * players that are meant to be hidden. The downside, however, is that because the client has no + * knowledge of that specific avatar whatsoever, un-hiding while the player is moving is not as + * smooth as with the appearance variant, since it first appears as if the player teleported in. + */ public fun setHidden(hidden: Boolean) { this.hidden = hidden } diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 519580352..46b5029a6 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -685,6 +685,12 @@ public class PlayerInfo internal constructor( if (other.avatar.hidden) { return false } + // If the avatar was allocated on this cycle, ensure we remove (and potentially re-add later) + // this avatar. This is due to someone logging out and another player taking the avatar the same + // cycle - which would otherwise potentially go by unnoticed, with the client assuming nothing changed. + if (other.avatar.allocateCycle == PlayerInfoProtocol.cycleCount) { + return false + } val coord = other.avatar.currentCoord if (!details.renderCoord.inDistance(coord, this.avatar.resizeRange)) { return false @@ -800,13 +806,14 @@ public class PlayerInfo internal constructor( this.localIndex = index avatar.extendedInfo.localIndex = index this.oldSchoolClientType = oldSchoolClientType + avatar.reset() + this.avatar.allocateCycle = PlayerInfoProtocol.cycleCount this.activeWorldId = ROOT_WORLD // There is always a root world! val rootDetails = protocol.detailsStorage.poll(ROOT_WORLD) details[PROTOCOL_CAPACITY] = rootDetails if (newInstance) return - avatar.reset() rootDetails.lowResolutionIndices.fill(0) rootDetails.lowResolutionCount = 0 rootDetails.highResolutionIndices.fill(0) diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 58d4069d4..17e8ba9ee 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -128,6 +128,7 @@ public class PlayerInfoProtocol( prepareExtendedInfo() putExtendedInfo() postUpdate() + cycleCount++ } /** @@ -281,5 +282,18 @@ public class PlayerInfoProtocol( */ public const val PROTOCOL_CAPACITY: Int = 2048 private val logger: InlineLogger = InlineLogger() + + /** + * The number of Player info update cycles that have occurred. + * We need to track this to avoid a nasty bug with servers de-allocating + re-allocating + * an avatar on the same cycle, in a small area. The effective bug is that another player takes + * ones' avatar, which leads to info protocol thinking nothing has changed (assuming the new player + * is still within range of the old one, enough to be in high resolution). + * + * We solve this by forcibly removing a player from high resolution view if the avatar was + * allocated on current cycle. If the player is still within range, they will be re-added + * later on in the cycle via low resolution updates - but correctly this time around! + */ + internal var cycleCount: Int = 0 } } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt index 8c196f49e..c286b7ca3 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerAvatar.kt @@ -87,8 +87,32 @@ public class PlayerAvatar internal constructor( huffmanCodec, ) + /** + * Whether this avatar is completed hidden from everyone else. Note that this completely skips + * sending any information to the client about this given avatar, it is not the same as soft + * hiding via the appearance extended info. + */ internal var hidden: Boolean = false + /** + * The [PlayerInfoProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a player, + * which can happen when a player is deallocated and reallocated on the same cycle, + * which could result in other players not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = PlayerInfoProtocol.cycleCount + + /** + * Sets the avatar as hidden (or unhidden, depending on [hidden]). When hidden via this function, + * no information is transmitted to the clients about this avatar. It is a hard-hiding function, + * unlike the one via appearance extended info, which strictly only hides client-side, but all + * clients still receive information about the client existing. + * The benefit to this function is that no plugins or RuneLite implementations can snoop on other + * players that are meant to be hidden. The downside, however, is that because the client has no + * knowledge of that specific avatar whatsoever, un-hiding while the player is moving is not as + * smooth as with the appearance variant, since it first appears as if the player teleported in. + */ public fun setHidden(hidden: Boolean) { this.hidden = hidden } diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt index 3cfa74b2b..4f39fbd5e 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfo.kt @@ -707,6 +707,12 @@ public class PlayerInfo internal constructor( if (other.avatar.hidden) { return false } + // If the avatar was allocated on this cycle, ensure we remove (and potentially re-add later) + // this avatar. This is due to someone logging out and another player taking the avatar the same + // cycle - which would otherwise potentially go by unnoticed, with the client assuming nothing changed. + if (other.avatar.allocateCycle == PlayerInfoProtocol.cycleCount) { + return false + } val worldId = other.avatar.worldId val details = getDetailsOrNull(worldId) ?: return false val coord = other.avatar.currentCoord @@ -809,6 +815,7 @@ public class PlayerInfo internal constructor( avatar.extendedInfo.localIndex = index this.oldSchoolClientType = oldSchoolClientType avatar.reset() + this.avatar.allocateCycle = PlayerInfoProtocol.cycleCount lowResolutionIndices.fill(0) lowResolutionCount = 0 highResolutionIndices.fill(0) diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt index 246a5b7ed..7aba114b7 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/playerinfo/PlayerInfoProtocol.kt @@ -122,6 +122,7 @@ public class PlayerInfoProtocol( prepareExtendedInfo() putExtendedInfo() postUpdate() + cycleCount++ } /** @@ -258,5 +259,18 @@ public class PlayerInfoProtocol( */ public const val PROTOCOL_CAPACITY: Int = 2048 private val logger: InlineLogger = InlineLogger() + + /** + * The number of Player info update cycles that have occurred. + * We need to track this to avoid a nasty bug with servers de-allocating + re-allocating + * an avatar on the same cycle, in a small area. The effective bug is that another player takes + * ones' avatar, which leads to info protocol thinking nothing has changed (assuming the new player + * is still within range of the old one, enough to be in high resolution). + * + * We solve this by forcibly removing a player from high resolution view if the avatar was + * allocated on current cycle. If the player is still within range, they will be re-added + * later on in the cycle via low resolution updates - but correctly this time around! + */ + internal var cycleCount: Int = 0 } } From 44455ad0974e749ecf26842348516225f52afb61 Mon Sep 17 00:00:00 2001 From: Kris Date: Fri, 13 Sep 2024 20:18:40 +0300 Subject: [PATCH 3/3] fix: issue with reallocating worldentity info on same cycle This fixes a potential issue where servers might end up deallocating and reallocating the same index worldentity info object on the same cycle. Same concept as the commit above this for player info, and the one from a few days ago for npc info. --- .../info/worldentityinfo/WorldEntityAvatar.kt | 9 +++++++ .../WorldEntityAvatarRepository.kt | 25 +++---------------- .../info/worldentityinfo/WorldEntityInfo.kt | 9 ++++++- .../worldentityinfo/WorldEntityProtocol.kt | 15 ++++++++++- .../info/worldentityinfo/WorldEntityAvatar.kt | 9 +++++++ .../WorldEntityAvatarRepository.kt | 25 +++---------------- .../info/worldentityinfo/WorldEntityInfo.kt | 9 ++++++- .../worldentityinfo/WorldEntityProtocol.kt | 15 ++++++++++- .../info/worldentityinfo/WorldEntityAvatar.kt | 9 +++++++ .../WorldEntityAvatarRepository.kt | 25 +++---------------- .../info/worldentityinfo/WorldEntityInfo.kt | 9 ++++++- .../worldentityinfo/WorldEntityProtocol.kt | 15 ++++++++++- .../info/worldentityinfo/WorldEntityAvatar.kt | 9 +++++++ .../WorldEntityAvatarRepository.kt | 25 +++---------------- .../info/worldentityinfo/WorldEntityInfo.kt | 9 ++++++- .../worldentityinfo/WorldEntityProtocol.kt | 15 ++++++++++- 16 files changed, 136 insertions(+), 96 deletions(-) diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatar.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatar.kt index c43f25e0a..e3d6cff00 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatar.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatar.kt @@ -51,6 +51,15 @@ public class WorldEntityAvatar( internal var highResolutionBuffer: ByteBuf? = null + /** + * The [WorldEntityProtocol.cycleCount] when this avatar was allocated. + * We use this to determine whether to perform a re-synchronization of a worldentity, + * which can happen when a worldentity is deallocated and reallocated on the same cycle, + * which could result in other clients not seeing any change take place. While rare, + * this possibility exists, and it could result in some rather odd bugs. + */ + internal var allocateCycle: Int = WorldEntityProtocol.cycleCount + /** * Precomputes the high resolution buffer of this world entity. */ diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatarRepository.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatarRepository.kt index c1c654f4f..72220db0e 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatarRepository.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityAvatarRepository.kt @@ -15,19 +15,12 @@ import java.lang.ref.SoftReference * As a soft reference queue, it will hold on-to the unused references until the JVM * absolutely needs the memory - before that, these can be reused, making it a perfect * use case for the pooling mechanism. - * @property releasedAvatarQueue the avatars that were released within this cycle. - * These avatars are initially put into a different structure as we cannot immediately - * release them - they could be picked up by something else the same cycle, which could - * lead to some weird bugs occurring. Instead, we wait for one cycle to pass before - * pushing them to the queue to be re-usable. This ensures no one is relying on this - * same instance still. */ public class WorldEntityAvatarRepository internal constructor( private val allocator: ByteBufAllocator, ) { private val elements: Array = arrayOfNulls(AVATAR_CAPACITY) private val queue: ReferenceQueue = ReferenceQueue() - private val releasedAvatarQueue: ArrayDeque = ArrayDeque() /** * Gets a world entity at the provided [idx], or null if it doesn't exist. @@ -67,6 +60,7 @@ public class WorldEntityAvatarRepository internal constructor( existing.currentCoord = CoordGrid(level, x, z) existing.lastCoord = existing.currentCoord existing.angle = angle + existing.allocateCycle = WorldEntityProtocol.cycleCount elements[index] = existing return existing } @@ -89,21 +83,8 @@ public class WorldEntityAvatarRepository internal constructor( */ public fun release(avatar: WorldEntityAvatar) { this.elements[avatar.index] = null - releasedAvatarQueue += avatar - } - - /** - * Transfers the recently released avatars over to the pool, so they can be re-used. - */ - internal fun transferAvatars() { - if (releasedAvatarQueue.isEmpty()) { - return - } - while (releasedAvatarQueue.isNotEmpty()) { - val avatar = releasedAvatarQueue.removeFirst() - val reference = SoftReference(avatar, queue) - reference.enqueue() - } + val reference = SoftReference(avatar, queue) + reference.enqueue() } internal companion object { diff --git a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index fad40b0c7..f1bc0695d 100644 --- a/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-222/osrs-222-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -277,7 +277,7 @@ public class WorldEntityInfo internal constructor( for (i in 0.. = arrayOfNulls(AVATAR_CAPACITY) private val queue: ReferenceQueue = ReferenceQueue() - private val releasedAvatarQueue: ArrayDeque = ArrayDeque() /** * Gets a world entity at the provided [idx], or null if it doesn't exist. @@ -67,6 +60,7 @@ public class WorldEntityAvatarRepository internal constructor( existing.currentCoord = CoordGrid(level, x, z) existing.lastCoord = existing.currentCoord existing.angle = angle + existing.allocateCycle = WorldEntityProtocol.cycleCount elements[index] = existing return existing } @@ -89,21 +83,8 @@ public class WorldEntityAvatarRepository internal constructor( */ public fun release(avatar: WorldEntityAvatar) { this.elements[avatar.index] = null - releasedAvatarQueue += avatar - } - - /** - * Transfers the recently released avatars over to the pool, so they can be re-used. - */ - internal fun transferAvatars() { - if (releasedAvatarQueue.isEmpty()) { - return - } - while (releasedAvatarQueue.isNotEmpty()) { - val avatar = releasedAvatarQueue.removeFirst() - val reference = SoftReference(avatar, queue) - reference.enqueue() - } + val reference = SoftReference(avatar, queue) + reference.enqueue() } internal companion object { diff --git a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index fad40b0c7..f1bc0695d 100644 --- a/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-223/osrs-223-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -277,7 +277,7 @@ public class WorldEntityInfo internal constructor( for (i in 0.. = arrayOfNulls(AVATAR_CAPACITY) private val queue: ReferenceQueue = ReferenceQueue() - private val releasedAvatarQueue: ArrayDeque = ArrayDeque() /** * Gets a world entity at the provided [idx], or null if it doesn't exist. @@ -67,6 +60,7 @@ public class WorldEntityAvatarRepository internal constructor( existing.currentCoord = CoordGrid(level, x, z) existing.lastCoord = existing.currentCoord existing.angle = angle + existing.allocateCycle = WorldEntityProtocol.cycleCount elements[index] = existing return existing } @@ -89,21 +83,8 @@ public class WorldEntityAvatarRepository internal constructor( */ public fun release(avatar: WorldEntityAvatar) { this.elements[avatar.index] = null - releasedAvatarQueue += avatar - } - - /** - * Transfers the recently released avatars over to the pool, so they can be re-used. - */ - internal fun transferAvatars() { - if (releasedAvatarQueue.isEmpty()) { - return - } - while (releasedAvatarQueue.isNotEmpty()) { - val avatar = releasedAvatarQueue.removeFirst() - val reference = SoftReference(avatar, queue) - reference.enqueue() - } + val reference = SoftReference(avatar, queue) + reference.enqueue() } internal companion object { diff --git a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index fad40b0c7..f1bc0695d 100644 --- a/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-224/osrs-224-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -277,7 +277,7 @@ public class WorldEntityInfo internal constructor( for (i in 0.. = arrayOfNulls(AVATAR_CAPACITY) private val queue: ReferenceQueue = ReferenceQueue() - private val releasedAvatarQueue: ArrayDeque = ArrayDeque() /** * Gets a world entity at the provided [idx], or null if it doesn't exist. @@ -67,6 +60,7 @@ public class WorldEntityAvatarRepository internal constructor( existing.currentCoord = CoordGrid(level, x, z) existing.lastCoord = existing.currentCoord existing.angle = angle + existing.allocateCycle = WorldEntityProtocol.cycleCount elements[index] = existing return existing } @@ -89,21 +83,8 @@ public class WorldEntityAvatarRepository internal constructor( */ public fun release(avatar: WorldEntityAvatar) { this.elements[avatar.index] = null - releasedAvatarQueue += avatar - } - - /** - * Transfers the recently released avatars over to the pool, so they can be re-used. - */ - internal fun transferAvatars() { - if (releasedAvatarQueue.isEmpty()) { - return - } - while (releasedAvatarQueue.isNotEmpty()) { - val avatar = releasedAvatarQueue.removeFirst() - val reference = SoftReference(avatar, queue) - reference.enqueue() - } + val reference = SoftReference(avatar, queue) + reference.enqueue() } internal companion object { diff --git a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt index fad40b0c7..f1bc0695d 100644 --- a/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt +++ b/protocol/osrs-225/osrs-225-model/src/main/kotlin/net/rsprot/protocol/game/outgoing/info/worldentityinfo/WorldEntityInfo.kt @@ -277,7 +277,7 @@ public class WorldEntityInfo internal constructor( for (i in 0..