Skip to content

Add optional async mob pathfinding#14029

Open
Rxflex wants to merge 1 commit into
PaperMC:mainfrom
Rxflex:feature/async-pathfinding
Open

Add optional async mob pathfinding#14029
Rxflex wants to merge 1 commit into
PaperMC:mainfrom
Rxflex:feature/async-pathfinding

Conversation

@Rxflex

@Rxflex Rxflex commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in feature that moves the expensive A* search in mob pathfinding
(PathFinder#findPath) off the main server thread onto a worker pool. Gated
per-world behind entities.behavior.async-pathfinding (default off).

How it works

  • The request is prepared on the main thread (validation, world-border
    check, EntityPathfindEvent, PathNavigationRegion snapshot).
  • Only the expensive A* search runs on a worker pool, using a
    thread-confined PathFinder created per call — the shared, mutable
    NodeEvaluator on this.pathFinder is never touched off-thread.
  • The finished Path is applied back on the main thread via the server
    executor, so the world mutation model is unchanged.
  • Only the create-and-apply moveTo(coords/entity, speed) overloads opt in;
    createPath(...) stays fully synchronous, so goals that inspect the path
    directly keep exact vanilla behaviour.

Safety / no-regression

  • Exceptions in the worker fall back silently (mob recomputes next time).
  • The pool uses a caller-runs policy with a bounded queue, so a saturated
    queue degrades to vanilla synchronous pathfinding — never worse than baseline.
  • Default off; zero behavioural change unless explicitly enabled.

Measured impact

Synthetic in-process benchmark (200 mobs, obstacle field, hard targets),
per-path main-thread cost:

main-thread cost / path
sync (async off) ~1003 µs
async (async on) ~91 µs

~90% of main-thread pathfinding cost removed on expensive paths (the case
that actually causes MSPT spikes: obstacle-dense areas, hard/unreachable
targets, villages/farms). On trivial paths the offload overhead makes it
roughly neutral — hence the opt-in default.

Notes

  • This is an experimental, opt-in feature; it has not yet been validated under
    a real multiplayer load, only via the isolated microbenchmark above.
  • Reading the live region/mob off-thread carries an accepted, bounded
    data-race tradeoff (as shipped by other forks), mitigated by the try/catch
    fallback.
  • Follow-up: a thread-local PathFinder pool would cut the residual ~91 µs
    prep (dominated by per-call NodeEvaluator allocation) further.

Offload the A* search in PathFinder#findPath off the main server thread
for mob navigation, gated per-world behind
entities.behavior.async-pathfinding (default off).

The request is prepared on the main thread (validation, EntityPathfindEvent,
region snapshot) and only the expensive A* search runs on a worker pool
using a thread-confined PathFinder instance; the finished Path is applied
back on the main thread via the server executor. Failures fall back
silently and the pool uses a caller-runs policy, so a saturated queue
degrades to vanilla synchronous behaviour and never regresses past it.

On expensive pathfinding (obstacle-dense areas, hard/unreachable targets)
this removes ~90% of the per-path main-thread cost (measured ~1003us -> ~91us
per path in a synthetic obstacle field). On trivial paths the offload
overhead makes it roughly neutral, hence the opt-in default.
@Rxflex Rxflex requested a review from a team as a code owner July 3, 2026 23:10
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

1 participant