async improvement
This commit is contained in:
parent
3fe7c0b37d
commit
9de6293b1a
4 changed files with 101 additions and 34 deletions
|
|
@ -62,7 +62,7 @@ public class MinimapCapture : IFrameConsumer, IDisposable
|
|||
var frame = ProcessBgr(bgr);
|
||||
if (frame == null) return;
|
||||
|
||||
Log.Information("Process: mode={Mode} cropSize={W}x{H} classifiedSize={CW}x{CH} wallSize={WW}x{WH}",
|
||||
Log.Debug("Process: mode={Mode} cropSize={W}x{H} classifiedSize={CW}x{CH} wallSize={WW}x{WH}",
|
||||
_detectedMode, bgr.Width, bgr.Height,
|
||||
frame.ClassifiedMat.Width, frame.ClassifiedMat.Height,
|
||||
frame.WallMask.Width, frame.WallMask.Height);
|
||||
|
|
|
|||
|
|
@ -14,11 +14,16 @@ public class NavigationExecutor : IDisposable
|
|||
private readonly MinimapCapture _capture;
|
||||
private readonly WorldMap _worldMap;
|
||||
private NavigationState _state = NavigationState.Idle;
|
||||
private bool _stopped;
|
||||
private volatile bool _stopped;
|
||||
private int _stuckCounter;
|
||||
private MapPosition? _lastPosition;
|
||||
private volatile byte[]? _cachedViewport;
|
||||
private static readonly Random Rng = new();
|
||||
|
||||
// Input loop communication (capture loop writes, input loop reads)
|
||||
private double _desiredDirX, _desiredDirY;
|
||||
private volatile bool _directionChanged;
|
||||
|
||||
public event Action<NavigationState>? StateChanged;
|
||||
public NavigationState State => _state;
|
||||
|
||||
|
|
@ -87,11 +92,13 @@ public class NavigationExecutor : IDisposable
|
|||
public async Task RunExploreLoop()
|
||||
{
|
||||
_stopped = false;
|
||||
_directionChanged = false;
|
||||
Log.Information("Starting explore loop");
|
||||
_cachedViewport = _worldMap.GetViewportSnapshot(_worldMap.Position);
|
||||
|
||||
var lastMoveTime = 0L;
|
||||
var lastClickTime = 0L;
|
||||
var heldKeys = new HashSet<int>(); // currently held WASD keys
|
||||
// Input loop runs concurrently — handles WASD keys + combat clicks
|
||||
var inputTask = RunInputLoop();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -116,7 +123,11 @@ public class NavigationExecutor : IDisposable
|
|||
var mode = _capture.DetectedMode;
|
||||
var pos = _worldMap.MatchAndStitch(frame.ClassifiedMat, frame.WallMask, mode);
|
||||
if (_worldMap.LastMatchSucceeded)
|
||||
{
|
||||
_capture.CommitWallColors();
|
||||
// Only re-render viewport when canvas was modified (avoids ~3ms PNG encode on dedup-skips)
|
||||
_cachedViewport = _worldMap.GetViewportSnapshot(pos);
|
||||
}
|
||||
|
||||
// Stuck detection
|
||||
if (_lastPosition != null)
|
||||
|
|
@ -130,7 +141,7 @@ public class NavigationExecutor : IDisposable
|
|||
}
|
||||
_lastPosition = pos;
|
||||
|
||||
// 2. Movement decisions (faster re-evaluation when stuck)
|
||||
// 2. Movement decisions — non-blocking, just post direction to input loop
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var moveInterval = _stuckCounter >= _config.StuckFrameCount
|
||||
? 200
|
||||
|
|
@ -156,24 +167,14 @@ public class NavigationExecutor : IDisposable
|
|||
}
|
||||
|
||||
SetState(NavigationState.Moving);
|
||||
await UpdateWasdKeys(heldKeys, direction.Value.dirX, direction.Value.dirY);
|
||||
// Post direction to input loop (non-blocking)
|
||||
_desiredDirX = direction.Value.dirX;
|
||||
_desiredDirY = direction.Value.dirY;
|
||||
_directionChanged = true;
|
||||
|
||||
if (_stuckCounter >= _config.StuckFrameCount)
|
||||
_stuckCounter = 0; // reset after re-routing
|
||||
}
|
||||
|
||||
// 3. Occasional combat clicks near screen center
|
||||
if (now - lastClickTime >= 1000 + Rng.Next(1000))
|
||||
{
|
||||
lastClickTime = now;
|
||||
var cx = 1280 + Rng.Next(-150, 150);
|
||||
var cy = 720 + Rng.Next(-150, 150);
|
||||
await _game.LeftClickAt(cx, cy);
|
||||
await Helpers.Sleep(100 + Rng.Next(100));
|
||||
cx = 1280 + Rng.Next(-150, 150);
|
||||
cy = 720 + Rng.Next(-150, 150);
|
||||
await _game.RightClickAt(cx, cy);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -192,9 +193,9 @@ public class NavigationExecutor : IDisposable
|
|||
}
|
||||
finally
|
||||
{
|
||||
// Always release held keys
|
||||
foreach (var key in heldKeys)
|
||||
await _game.KeyUp(key);
|
||||
_stopped = true; // signal input loop to exit
|
||||
_cachedViewport = null;
|
||||
await inputTask; // wait for input loop to release keys
|
||||
}
|
||||
|
||||
if (_state != NavigationState.Completed)
|
||||
|
|
@ -203,6 +204,57 @@ public class NavigationExecutor : IDisposable
|
|||
Log.Information("Explore loop ended");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs concurrently with the capture loop. Owns all game input:
|
||||
/// WASD key holds (from direction posted by capture loop) and periodic combat clicks.
|
||||
/// </summary>
|
||||
private async Task RunInputLoop()
|
||||
{
|
||||
var heldKeys = new HashSet<int>();
|
||||
var nextCombatTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 1000 + Rng.Next(1000);
|
||||
|
||||
try
|
||||
{
|
||||
while (!_stopped)
|
||||
{
|
||||
// Apply direction changes from capture loop
|
||||
if (_directionChanged)
|
||||
{
|
||||
_directionChanged = false;
|
||||
var dirX = _desiredDirX;
|
||||
var dirY = _desiredDirY;
|
||||
await UpdateWasdKeys(heldKeys, dirX, dirY);
|
||||
}
|
||||
|
||||
// Combat clicks on timer
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
if (now >= nextCombatTime)
|
||||
{
|
||||
nextCombatTime = now + 1000 + Rng.Next(1000);
|
||||
var cx = 1280 + Rng.Next(-150, 150);
|
||||
var cy = 720 + Rng.Next(-150, 150);
|
||||
await _game.LeftClickAt(cx, cy);
|
||||
await Helpers.Sleep(100 + Rng.Next(100));
|
||||
cx = 1280 + Rng.Next(-150, 150);
|
||||
cy = 720 + Rng.Next(-150, 150);
|
||||
await _game.RightClickAt(cx, cy);
|
||||
}
|
||||
|
||||
await Helpers.Sleep(15);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in input loop");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always release held keys
|
||||
foreach (var key in heldKeys)
|
||||
await _game.KeyUp(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a direction vector to WASD key holds. Releases keys no longer needed,
|
||||
/// presses new ones. Supports diagonal movement (two keys at once).
|
||||
|
|
@ -261,7 +313,12 @@ public class NavigationExecutor : IDisposable
|
|||
public bool IsExploring => _state != NavigationState.Idle && _state != NavigationState.Completed && _state != NavigationState.Failed;
|
||||
public MapPosition Position => _worldMap.Position;
|
||||
public byte[] GetMapSnapshot() => _worldMap.GetMapSnapshot();
|
||||
public byte[] GetViewportSnapshot(int viewSize = 400) => _worldMap.GetViewportSnapshot(_worldMap.Position, viewSize);
|
||||
public byte[] GetViewportSnapshot(int viewSize = 400)
|
||||
{
|
||||
var cached = _cachedViewport;
|
||||
if (cached != null) return cached;
|
||||
return _worldMap.GetViewportSnapshot(_worldMap.Position, viewSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Capture one frame via pipeline, track position, stitch into world map.
|
||||
|
|
@ -305,7 +362,7 @@ public class NavigationExecutor : IDisposable
|
|||
result = _capture.CaptureStage(stage);
|
||||
var renderMs = sw.Elapsed.TotalMilliseconds - renderStart;
|
||||
|
||||
Log.Information("ProcessFrame: capture={Capture:F1}ms stitch={Stitch:F1}ms render={Render:F1}ms total={Total:F1}ms",
|
||||
Log.Debug("ProcessFrame: capture={Capture:F1}ms stitch={Stitch:F1}ms render={Render:F1}ms total={Total:F1}ms",
|
||||
captureMs, stitchMs, renderMs, sw.Elapsed.TotalMilliseconds);
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public class WorldMap : IDisposable
|
|||
var changedPixels = Cv2.CountNonZero(xor);
|
||||
if (changedPixels < _config.FrameChangeThreshold)
|
||||
{
|
||||
Log.Information("Frame dedup: {Changed} changed pixels, skipping ({Ms:F1}ms)",
|
||||
Log.Debug("Frame dedup: {Changed} changed pixels, skipping ({Ms:F1}ms)",
|
||||
changedPixels, sw.Elapsed.TotalMilliseconds);
|
||||
return _position;
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ public class WorldMap : IDisposable
|
|||
|
||||
var posDx = _position.X - prevPos.X;
|
||||
var posDy = _position.Y - prevPos.Y;
|
||||
Log.Information("MatchAndStitch: mode={Mode} pos=({X:F1},{Y:F1}) moved=({Dx:F1},{Dy:F1}) dedup={Dedup:F1}ms match={Match:F1}ms stitch={Stitch:F1}ms total={Total:F1}ms",
|
||||
Log.Debug("MatchAndStitch: mode={Mode} pos=({X:F1},{Y:F1}) moved=({Dx:F1},{Dy:F1}) dedup={Dedup:F1}ms match={Match:F1}ms stitch={Stitch:F1}ms total={Total:F1}ms",
|
||||
mode, _position.X, _position.Y, posDx, posDy, dedupMs, matchMs, stitchMs, sw.Elapsed.TotalMilliseconds);
|
||||
return _position;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,16 +83,26 @@ public sealed class DesktopDuplication : IScreenCapture
|
|||
|
||||
var mapped = _context.Map(_staging!, 0, MapMode.Read);
|
||||
|
||||
var mat = Mat.FromPixelData(h, w, MatType.CV_8UC4, mapped.DataPointer, (int)mapped.RowPitch);
|
||||
// GPU copy is complete once Map returns — release DXGI frame immediately
|
||||
// so the DWM compositor can recycle the buffer (~0.5ms hold vs ~2.5ms before).
|
||||
srcTexture.Dispose();
|
||||
resource.Dispose();
|
||||
_duplication!.ReleaseFrame();
|
||||
|
||||
var duplication = _duplication!;
|
||||
return new ScreenFrame(mat, () =>
|
||||
// CPU copy from our staging texture (no DXGI dependency, ~1.5ms for 2560×1440)
|
||||
var mat = new Mat(h, w, MatType.CV_8UC4);
|
||||
unsafe
|
||||
{
|
||||
_context.Unmap(_staging!, 0);
|
||||
srcTexture.Dispose();
|
||||
resource.Dispose();
|
||||
duplication.ReleaseFrame();
|
||||
});
|
||||
var src = (byte*)mapped.DataPointer;
|
||||
var dst = (byte*)mat.Data;
|
||||
var rowBytes = w * 4;
|
||||
var pitch = (int)mapped.RowPitch;
|
||||
for (var row = 0; row < h; row++)
|
||||
Buffer.MemoryCopy(src + row * pitch, dst + row * rowBytes, rowBytes, rowBytes);
|
||||
}
|
||||
|
||||
_context.Unmap(_staging!, 0);
|
||||
return new ScreenFrame(mat, () => mat.Dispose());
|
||||
}
|
||||
|
||||
public unsafe Mat? CaptureRegion(Region region)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue