-
-
Notifications
You must be signed in to change notification settings - Fork 138
Description
RimWorld's PathFinder schedules pathing jobs on background threads during MapPreTick(), but only forces them to complete at the start of the next tick. These jobs check door forbidden state via IsForbiddenToPass which depends on MP's FactionContext. However, FactionContext is constantly pushed/popped during the rest of AsyncTimeComp.Tick() like pawns tick. Race condition here.
This will cause 2 playerfaction in 1 map with differnt unforbidden data get desync randomly.
I got 2 evidences.
- Normally I got about 1 desync per 2 days, desync log says 1 player failed to pathing and uses rand to recalculate path, while the other player don't have this rand trace. And then I choose to unforbidden all doors both player, for 15days, no same desync
- I made a temp fix about this, runs with different forbidden data for days and it didn't desync.
Details
In AsyncTimeComp calls Verse.Map.MapPreTick calls this.pathFinder.PathFinderTick()
In Verse.AI.PathFinder.PathFinderTick():
calls this.ForceCompleteScheduledJobs() to force finish threads work last tick scheduled
calls JobHandle lastGridHandle = this.ScheduleGridJobs();
calls this.ScheduleBatchedPathJobs(lastGridHandle)
calls this.GetDoorBlockedJob(request, ref state).Schedule(jobHandle) //start working
Which means these jobs can be done whenever before next PathFinderTick()
In GetDoorBlockedJob => PathGridDoorsBlockedJob => Execute => PathUtility.GetDoorCost => door.IsForbiddenToPass(pawn) => t.IsForbidden(pawn.Faction) => (Faction.OfPlayer && (compForbiddable.Forbidden => MpPrefix))
AsyncTimeComp's tickList.tick() can raise map.PushFaction & PopFaction e.g. Ticking Thing(Pawn) with Faction
My solution for now
- Call ForceCompleteScheduledJobs() anywhere after ScheduleBatchedPathJobs(lastGridHandle) done and before other ticking stuffs tick(before tickListNormal.tick()).
- reimplement IsForbidden(thing, faction) wisely to check if thing.faction.IsPlayer && map.GetCustomFactionData(faction).unforbidden.Contains(thing)
this changes very little and performance well but changes IsForbidden very much.
I wonder if there's a way to cache forbiddendata for jobs. but forbiddendata can also be changed by player SetForbid, cache that would easily cause pathFailed() and the pawn have to "wait" 30ticks.