Skip to content

Commit

Permalink
Pathfind cancellation support.
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed Mar 5, 2024
1 parent 036c079 commit a58a424
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 17 deletions.
1 change: 1 addition & 0 deletions vnavmesh/Debug/DebugNavmeshManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public void Draw()
}
ImGui.SameLine();
ImGui.TextUnformatted(_manager.CurrentKey);
ImGui.TextUnformatted($"Num pathfinding tasks: {(_manager.PathfindInProgress ? 1 : 0)} in progress, {_manager.NumQueuedPathfindRequests} queued");

if (_manager.Navmesh == null || _manager.Query == null)
return;
Expand Down
11 changes: 11 additions & 0 deletions vnavmesh/IPCProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;

namespace Navmesh
{
Expand All @@ -16,6 +17,9 @@ public IPCProvider(NavmeshManager navmeshManager, FollowPath followPath, AsyncMo
RegisterFunc("Nav.Reload", () => navmeshManager.Reload(true));
RegisterFunc("Nav.Rebuild", () => navmeshManager.Reload(false));
RegisterFunc("Nav.Pathfind", (Vector3 from, Vector3 to, bool fly) => navmeshManager.QueryPath(from, to, fly));
RegisterFunc("Nav.PathfindCancelable", (Vector3 from, Vector3 to, bool fly, CancellationToken cancel) => navmeshManager.QueryPath(from, to, fly, cancel));
RegisterFunc("Nav.PathfindInProgress", () => navmeshManager.PathfindInProgress);
RegisterFunc("Nav.PathfindNumQueued", () => navmeshManager.NumQueuedPathfindRequests);
RegisterFunc("Nav.IsAutoLoad", () => navmeshManager.AutoLoad);
RegisterAction("Nav.SetAutoLoad", (bool v) => navmeshManager.AutoLoad = v);

Expand Down Expand Up @@ -77,6 +81,13 @@ private void RegisterFunc<TRet, T1, T2, T3>(string name, Func<T1, T2, T3, TRet>
_disposeActions.Add(p.UnregisterFunc);
}

private void RegisterFunc<TRet, T1, T2, T3, T4>(string name, Func<T1, T2, T3, T4, TRet> func)
{
var p = Service.PluginInterface.GetIpcProvider<T1, T2, T3, T4, TRet>("vnavmesh." + name);
p.RegisterFunc(func);
_disposeActions.Add(p.UnregisterFunc);
}

private void RegisterAction(string name, Action func)
{
var p = Service.PluginInterface.GetIpcProvider<object>("vnavmesh." + name);
Expand Down
62 changes: 45 additions & 17 deletions vnavmesh/NavmeshManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class NavmeshManager : IDisposable
public NavmeshQuery? Query => _query;
public float LoadTaskProgress => _loadTask != null ? _loadTaskProgress : -1; // returns negative value if task is not running
public string CurrentKey => _lastKey;
public bool PathfindInProgress => _currentPathfindTask != null;
public int NumQueuedPathfindRequests => _queuedPathfindTasks.Count;

private DirectoryInfo _cacheDir;
private NavmeshSettings _settings = new();
Expand All @@ -29,6 +31,8 @@ public class NavmeshManager : IDisposable
private Navmesh? _navmesh;
private NavmeshQuery? _query;
private CancellationTokenSource? _queryCancelSource;
private List<Task<List<Vector3>>> _queuedPathfindTasks = new(); // will be executed one by one in order after current task completes
private Task<List<Vector3>>? _currentPathfindTask;

public NavmeshManager(DirectoryInfo cacheDir)
{
Expand All @@ -53,7 +57,7 @@ public void Update()
if (_loadTask != null)
{
if (!_loadTask.IsCompleted)
return; // async task is still in progress, do nothing; note that we don't want to start multiple concurrent tasks on rapid transitions
return; // async mesh load task is still in progress, do nothing; note that we don't want to start multiple concurrent tasks on rapid transitions

Service.Log.Information($"Finishing transition to '{_lastKey}'");
try
Expand All @@ -72,19 +76,38 @@ public void Update()
}

var curKey = GetCurrentKey();
if (curKey == _lastKey)
return; // everything up-to-date

if (!AutoLoad)
if (curKey != _lastKey)
{
if (_lastKey.Length == 0)
return; // nothing is loaded, and auto-load is forbidden
curKey = ""; // just unload existing mesh
// navmesh needs to be reloaded
if (!AutoLoad)
{
if (_lastKey.Length == 0)
return; // nothing is loaded, and auto-load is forbidden
curKey = ""; // just unload existing mesh
}

Service.Log.Info($"Starting transition from '{_lastKey}' to '{curKey}'");
_lastKey = curKey;
Reload(true);
return; // mesh load is now in progress
}

Service.Log.Info($"Starting transition from '{_lastKey}' to '{curKey}'");
_lastKey = curKey;
Reload(true);
// at this point, we're not loading a mesh
if (_query != null)
{
if (_currentPathfindTask != null && _currentPathfindTask.IsCompleted)
{
_currentPathfindTask = null;
}

if (_currentPathfindTask == null && _queuedPathfindTasks.Count > 0)
{
// kick off new pathfind task
_currentPathfindTask = _queuedPathfindTasks[0];
_queuedPathfindTasks.RemoveAt(0);
_currentPathfindTask.Start();
}
}
}

public bool Reload(bool allowLoadFromCache)
Expand All @@ -107,7 +130,7 @@ public bool Reload(bool allowLoadFromCache)
return true;
}

public Task<List<Vector3>>? QueryPath(Vector3 from, Vector3 to, bool flying)
public Task<List<Vector3>>? QueryPath(Vector3 from, Vector3 to, bool flying, CancellationToken externalCancel = default)
{
var query = _query;
if (_queryCancelSource == null || query == null)
Expand All @@ -116,12 +139,15 @@ public bool Reload(bool allowLoadFromCache)
return null;
}

var token = _queryCancelSource.Token;
var task = Task.Run(() =>
// task can be cancelled either by internal request (i.e. when navmesh is reloaded) or external
var combined = CancellationTokenSource.CreateLinkedTokenSource(_queryCancelSource.Token, externalCancel);
var task = new Task<List<Vector3>>(() =>
{
token.ThrowIfCancellationRequested();
return flying ? query.PathfindVolume(from, to, UseRaycasts, UseStringPulling, token) : query.PathfindMesh(from, to, UseRaycasts, UseStringPulling, token);
using var autoDisposeCombined = combined;
combined.Token.ThrowIfCancellationRequested();
return flying ? query.PathfindVolume(from, to, UseRaycasts, UseStringPulling, combined.Token) : query.PathfindMesh(from, to, UseRaycasts, UseStringPulling, combined.Token);
});
_queuedPathfindTasks.Add(task);
return task;
}

Expand Down Expand Up @@ -154,8 +180,10 @@ private unsafe string GetCacheKey(SceneDefinition scene)
private void ClearState()
{
_queryCancelSource?.Cancel();
//_queryCancelSource?.Dispose(); - i don't think it's safe to call dispose at this point...
_queryCancelSource?.Dispose();
_queryCancelSource = null;
_queuedPathfindTasks.Clear();
_currentPathfindTask = null;

OnNavmeshChanged?.Invoke(null, null);
_query = null;
Expand Down

0 comments on commit a58a424

Please sign in to comment.