Skip to content

PathFinderTick can cause desync for 1.6. #837

@Sakura-TA

Description

@Sakura-TA

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.

  1. 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
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    1.6Fixes or bugs relating to 1.6 (Not Odyssey).desyncBug that specifically causes a desynced state.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions