Skip to content

Feature: Async Pathfinding Workers and Queue Bound

Tsu edited this page Jun 5, 2026 · 3 revisions

This feature expands pathfinding throughput with additional async workers and bounded queue cleanup.

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

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.

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.
  • 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