Skip to content

Clear PortraitsCache post faction creation#596

Merged
notfood merged 2 commits intorwmt:devfrom
Tick-git:Fix-for-#591-exception-in-update
Jul 22, 2025
Merged

Clear PortraitsCache post faction creation#596
notfood merged 2 commits intorwmt:devfrom
Tick-git:Fix-for-#591-exception-in-update

Conversation

@Tick-git
Copy link
Copy Markdown
Contributor

Label: 1.6, Bug

That was a bit of a pain one for me - I kept chasing the wrong direction while the real issue was elsewhere. I had a fix early on, but understanding why it happened took a while. :'( :D

Fixes #591

Issue:

Shortform:

Fixes an issue where pawn textures lingered in PortraitsCache due to thingID changes during faction creation in multifaction, causing hash mismatches. This led to errors when accessing stale pawn maps during the update loop - now resolved by clearing the cache post faction creation.

Longform:

In multifaction gameplay, when a new faction is created, we use Page_ConfigureStartingPawns, which internally calls PortraitsCache.Get. The PortraitsCache is responsible for caching pawn textures, stored in a Dictionary<Pawn, Texture>. These textures are automatically cleaned up if they haven't been accessed for a few ticks.

To manage this dictionary, Pawn.GetHashCode (or more generally Thing.GetHashCode) is overridden to return the thingID. However, 1.6 changes how thingID is handled. As a result, cached textures in the PortraitsCache become orphaned due to mismatched hash codes.

Specifically, when a pawn is sent over the network after faction creation has started, it is serialized and exposed to all players, including the issuer. During this process - particularly in ScribeUtil.ReadExposable - the thingID is initially a negative number (as expected, matching the value from Page_ConfigureStartingPawns). In version 1.5, these thingIDs remained negative throughout this process (at least for the relevant duration). However, in 1.6, the thingID is replaced with a large positive number after FinalizeLoading.

This change causes the pawn’s hash code to differ, so it can no longer be found in its original dictionary entry in the PortraitsCache. As a result, those cached textures are no longer properly tracked or removed.

The main problem occurs when a new game is loaded and PortraitsCache still contains pawns from the previous session. During the update loop, the method IsAnimated is called, which we patch via the PawnPortraitMapTime class to access the pawn’s map. But because the pawn is from an old game, its map no longer exists - resulting in the error in this issue.

Fix:

The solution is to clear the portrait cache after the faction has been created. Although the cache is already cleared once before Page_ConfigureStartingPawns closes, the update loop continues for a few more frames - during which the affected pawns are regenerated in the cache. I think in singleplayer, this isn’t an issue, as those textures are removed again shortly afterward. However, in our multiplayer case, the timing causes problems.

Tick-git added 2 commits July 22, 2025 19:54
Fixes rwmt#591

Shortform:

Fixes an issue where pawn textures lingered in  `PortraitsCache` due to `thingID` changes during faction creation, causing hash mismatches. In multiplayer, this led to errors when accessing stale pawn maps during the update loop - now resolved by clearing the cache post faction creation.

Longform:

In multifaction gameplay, when a new faction is created, we use `Page_ConfigureStartingPawns`, which internally calls `PortraitsCache.Get`. The `PortraitsCache` is responsible for caching pawn textures, stored in a `Dictionary<Pawn, Texture>`. These textures are automatically cleaned up if they haven't been accessed for a few ticks.

To manage this dictionary, `Pawn.GetHashCode` (or more generally `Thing.GetHashCode`) is overridden to return the `thingID`. However, starting with version 1.6, changes in how `thingID` is handled have introduced inconsistencies. As a result, cached textures in the `PortraitsCache` become orphaned due to mismatched hash codes.

Specifically, when a pawn is sent over the network after faction creation has started, it is serialized and exposed to all players, including the issuer. During this process - particularly in `ScribeUtil.ReadExposable` - the `thingID` is initially a negative number (as expected, matching the value from `Page_ConfigureStartingPawns`). In version 1.5, these `thingID`s remained negative throughout this process (at least for the relevant duration). However, in 1.6, the `thingID` is replaced with a large positive number after `FinalizeLoading`.

This change causes the pawn’s hash code to differ, so it can no longer be found in its original dictionary entry in the `PortraitsCache`. As a result, those cached textures are no longer properly tracked or removed.

The main problem occurs when a new game is loaded and `PortraitsCache` still contains pawns from the previous session. During the update loop, the method `IsAnimated` is called, which we patch via the `PawnPortraitMapTime` class to access the pawn’s map. But because the pawn is from an old game, its map no longer exists - resulting in the error in this issue.

**Fix:**

The solution is to clear the portrait cache after the faction has been created. Although the cache is already cleared once before `Page_ConfigureStartingPawns` closes, the update loop continues for a few more frames - during which the affected pawns are regenerated in the cache. In singleplayer, this isn’t an issue, as those textures are removed again shortly afterward. However, in our multiplayer case, the timing causes problems.
@notfood notfood added fix Fixes for a bug or desync. 1.6 Fixes or bugs relating to 1.6 (Not Odyssey). labels Jul 22, 2025
@notfood notfood moved this to In review in 1.6 and Odyssey Jul 22, 2025
@notfood notfood linked an issue Jul 22, 2025 that may be closed by this pull request
@notfood notfood changed the title Fix for #591 exception in update Clear cache post faction creation Jul 22, 2025
@notfood notfood changed the title Clear cache post faction creation Clear PortraitsCache post faction creation Jul 22, 2025
@notfood notfood merged commit cb05e1e into rwmt:dev Jul 22, 2025
1 check passed
@github-project-automation github-project-automation bot moved this from In review to Done in 1.6 and Odyssey Jul 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.6 Fixes or bugs relating to 1.6 (Not Odyssey). fix Fixes for a bug or desync.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Root level exception in Update(): System.NullReferenceException

2 participants