Skip to content

Feature: Async Pathfinding Workers and Queue Bound

Tsu edited this page Jun 9, 2026 · 3 revisions

This feature expands pathfinding throughput with additional async workers, bounded queue cleanup, queue priority, and short failure cooldowns.

Where It Lives

Main patch:

  • patches/VSEssentials/Entity/Pathfinding/PathfindingAsync.cs.patch

Extra Worker Threads

The patch adds PathfindingAsyncWorker, each with its own AStar instance:

internal class PathfindingAsyncWorker : IAsyncServerSystem
{
    private readonly PathfindingAsync owner;
    public AStar Astar;

    public void OnSeparateThreadTick()
    {
        owner.ProcessQueue(Astar, 100);
    }
}

PathfindingAsync still registers the built-in thread:

api.Server.AddServerThread("ai-pathfinding", this);

Then Stratum may add more:

for (int i = 0; i < extra; i++)
{
    var worker = new PathfindingAsyncWorker(this, api);
    stratumExtraWorkers.Add(worker);
    api.Server.AddServerThread("ai-pathfinding-" + (i + 2), worker);
}

Config Bridge

The patch reflectively reads Stratum config from VintagestoryLib to avoid hard compile-time coupling from VSEssentials:

Assembly libAsm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "VintagestoryLib");
Type runtimeT = libAsm?.GetType("Vintagestory.Server.StratumRuntime");
object cfg = runtimeT?.GetProperty("Config", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null);

From Performance.Pathfinding, it reads:

  • Async
  • WorkerThreads
  • MaxQueued
  • MaxTaskAgeMs
  • PriorityEnabled
  • PriorityQueueThreshold
  • FarTaskDistanceBlocks
  • DropFarTasksUnderPressure
  • FailureCooldownAfterFailures
  • FailureCooldownMs

If WorkerThreads <= 0, it falls back to:

workers = Math.Max(2, Environment.ProcessorCount / 2);

Queue Bound and Stale Work Cleanup

When MaxQueued > 0, Stratum prunes the queue before enqueueing more work. It drops obsolete work first:

  • tasks already marked finished
  • tasks older than MaxTaskAgeMs
  • tasks whose source entity is gone, dead, inactive, or marked for despawn

Only after stale work is removed does it drop oldest remaining useful work to make room.

if (stratumMaxQueued > 0 && PathfinderTasks.Count >= stratumMaxQueued)
{
    StratumPrunePathfinderQueue(forceOneFreeSlot: true);
}
PathfinderTasks.Enqueue(task);

Workers also skip obsolete tasks when dequeueing:

while (PathfinderTasks.TryDequeue(out PathfinderTask task))
{
    if (!StratumIsObsoleteTask(task, out _))
    {
        return task;
    }

    StratumMarkDropped(task);
}

Pathfinder tasks carry the owning entity id and enqueue time so this cleanup can be precise.

Priority Under Queue Pressure

When PriorityEnabled is on and the queue is at or above PriorityQueueThreshold, Stratum gives nearby owner tasks first shot at worker time. Far tasks can be dropped under pressure when:

  • DropFarTasksUnderPressure is enabled
  • FarTaskDistanceBlocks is greater than 0
  • the task owner is farther than that distance from players

This keeps active nearby mobs from waiting behind far or stale path work.

PathfinderTask carries Stratum metadata for this:

  • SourceEntityId
  • EnqueuedMilliseconds
  • StratumNearestPlayerDistanceSq
  • StratumDropWhenFarUnderPressure

Replacing Old Queued Goals

WaypointsTraverser cancels an older queued async goal when the same traverser starts a new one. This avoids spending worker time on a path the entity already stopped needing.

Repeated Failure Cooldown

After repeated no-path results, WaypointsTraverser can wait briefly before queueing the same kind of work again:

  • FailureCooldownAfterFailures
  • FailureCooldownMs

This is meant for mobs stuck against the same impossible route. A short cooldown cuts queue churn without making normal movement feel delayed.

Behavior Summary

  • Pathfinding stays async.
  • The number of worker threads can scale above one.
  • Queue growth can be capped without spending worker time on stale requests.
  • Nearby work gets priority when the queue is backed up.
  • Far path work can be dropped under pressure.
  • Repeated failed goals can cool down briefly before retrying.
  • MaxTaskAgeMs = 0 disables age-based staleness while leaving owner/finished checks active.

Source of Truth

  • patches/VSEssentials/Entity/Pathfinding/PathfindingAsync.cs.patch
  • patches/VSEssentials/Entity/Pathfinding/Astar/WaypointsTraverser.cs.patch
  • patches/VintagestoryApi/Server/PathfinderTask.cs.patch
  • VSEssentials/Entity/Pathfinding/PathfindingAsync.cs
  • sources/VintagestoryLib/Vintagestory.Server/StratumConfig.cs (Performance.Pathfinding)

Clone this wiki locally