66using Microsoft . ML . OnnxRuntime . Tensors ;
77using Newtonsoft . Json . Linq ;
88using Other ;
9- using Supercluster . KDTree ;
109using System . Diagnostics ;
1110using System . Drawing ;
1211using System . Drawing . Imaging ;
1312using System . IO ;
1413using System . Runtime . CompilerServices ;
1514using System . Windows ;
1615using Visuality ;
17- using static Other . LogManager ;
1816using static Aimmy2 . AILogic . MathUtil ;
17+ using static Other . LogManager ;
1918
2019namespace Aimmy2 . AILogic
2120{
@@ -60,7 +59,7 @@ public void RequestSizeChange(int newSize)
6059 private KalmanPrediction kalmanPrediction ;
6160 private WiseTheFoxPrediction wtfpredictionManager ;
6261
63-
62+
6463
6564 // Display-aware properties
6665 private int ScreenWidth => DisplayManager . ScreenWidth ;
@@ -72,7 +71,10 @@ public void RequestSizeChange(int newSize)
7271 private readonly RunOptions ? _modeloptions ;
7372 private InferenceSession ? _onnxModel ;
7473
75- private Thread ? _aiLoopThread ;
74+ //private Thread? _aiLoopThread;
75+
76+ private Task ? _aiLoopTask ;
77+ private CancellationTokenSource ? _cts ;
7678 private volatile bool _isAiLoopRunning ;
7779
7880 // For Auto-Labelling Data System
@@ -372,14 +374,12 @@ private async Task InitializeModel(string modelPath)
372374 if ( _onnxModel ? . OutputMetadata != null && _onnxModel . OutputMetadata . Count > 0 )
373375 {
374376 Log ( LogLevel . Info , "Starting AI Loop" , false ) ;
375- _isAiLoopRunning = true ;
376- _aiLoopThread = new Thread ( AiLoop )
377- {
378- IsBackground = true ,
379- Priority = ThreadPriority . AboveNormal
380- } ;
381- _aiLoopThread . Start ( ) ;
382- // Begin the loop
377+ _cts ? . Cancel ( ) ;
378+
379+ try { _aiLoopTask ? . Wait ( 500 ) ; } catch { }
380+ _cts = new CancellationTokenSource ( ) ;
381+ _aiLoopTask = Task . Run ( ( ) => AiLoop ( _cts . Token ) , _cts . Token ) ;
382+ Log ( LogLevel . Info , "AI loop started" ) ;
383383 }
384384 else
385385 {
@@ -571,12 +571,12 @@ private static bool ShouldProcess() =>
571571 Dictionary . toggleState [ "Show Detected Player" ] ||
572572 Dictionary . toggleState [ "Auto Trigger" ] ;
573573
574- private async void AiLoop ( )
574+ private async Task AiLoop ( CancellationToken ct )
575575 {
576576 Stopwatch stopwatch = new ( ) ;
577577 DetectedPlayerWindow ? DetectedPlayerOverlay = Dictionary . DetectedPlayerOverlay ;
578578
579- while ( _isAiLoopRunning )
579+ while ( ! ct . IsCancellationRequested && _isAiLoopRunning )
580580 {
581581 // Check for pending size changes at the start of each iteration
582582 lock ( _sizeLock )
@@ -589,6 +589,7 @@ private async void AiLoop()
589589 }
590590
591591 stopwatch . Restart ( ) ;
592+
592593 _captureManager . HandlePendingDisplayChanges ( ) ;
593594
594595 using ( Benchmark ( "AILoopIteration" ) )
@@ -632,13 +633,13 @@ private async void AiLoop()
632633 else
633634 {
634635 // Processing so we are at the ready but not holding right/click.
635- await Task . Delay ( 1 ) ;
636+ await Task . Delay ( 1 , ct ) ;
636637 }
637638 }
638639 else
639640 {
640641 // No work to do—sleep briefly to free up CPU
641- await Task . Delay ( 1 ) ;
642+ await Task . Delay ( 1 , ct ) ;
642643 }
643644 }
644645
@@ -693,7 +694,7 @@ private async Task AutoTrigger()
693694 if ( ! Dictionary . toggleState [ "Aim Assist" ] || ! Dictionary . toggleState [ "Show Detected Player" ] ) return ;
694695
695696 }
696- private void CheckSprayRelease ( )
697+ private void CheckSprayRelease ( )
697698 {
698699 if ( ! Dictionary . toggleState [ "Spray Mode" ] ) return ;
699700
@@ -710,8 +711,6 @@ private void CheckSprayRelease()
710711 }
711712 }
712713
713-
714-
715714 private void CalculateCoordinates ( DetectedPlayerWindow DetectedPlayerOverlay , Prediction closestPrediction , float scaleX , float scaleY )
716715 {
717716 AIConf = closestPrediction . Confidence ;
@@ -889,9 +888,11 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
889888 float [ ] inputArray ;
890889 using ( Benchmark ( "BitmapToFloatArray" ) )
891890 {
892- if ( _reusableInputArray == null || _reusableInputArray . Length != 3 * IMAGE_SIZE * IMAGE_SIZE )
891+ int requiredLength = 3 * IMAGE_SIZE * IMAGE_SIZE ;
892+
893+ if ( _reusableInputArray == null || _reusableInputArray . Length != requiredLength )
893894 {
894- _reusableInputArray = new float [ 3 * IMAGE_SIZE * IMAGE_SIZE ] ;
895+ _reusableInputArray = new float [ requiredLength ] ;
895896 }
896897 inputArray = _reusableInputArray ;
897898
@@ -911,7 +912,11 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
911912 inputArray . AsSpan ( ) . CopyTo ( _reusableTensor . Buffer . Span ) ;
912913 }
913914
914- if ( _onnxModel == null ) return null ;
915+ if ( _onnxModel == null )
916+ {
917+ frame . Dispose ( ) ;
918+ return null ; // Model not loaded, exit early
919+ }
915920
916921 //IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results;
917922 Tensor < float > ? outputTensor = null ;
@@ -921,7 +926,7 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
921926 outputTensor = results [ 0 ] . AsTensor < float > ( ) ;
922927 }
923928
924- if ( outputTensor == null )
929+ if ( outputTensor == null )
925930 {
926931 Log ( LogLevel . Error , "Model inference returned null output tensor." , true , 2000 ) ;
927932 SaveFrame ( frame ) ;
@@ -941,7 +946,7 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
941946 {
942947 ( KDpoints , KDPredictions ) = PrepareKDTreeData ( outputTensor , detectionBox , fovMinX , fovMaxX , fovMinY , fovMaxY ) ;
943948 }
944-
949+
945950 if ( KDpoints . Count == 0 || KDPredictions . Count == 0 )
946951 {
947952 SaveFrame ( frame ) ;
@@ -969,6 +974,7 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
969974 {
970975 UpdateDetectionBox ( finalTarget , detectionBox ) ;
971976 SaveFrame ( frame , finalTarget ) ;
977+ frame . Dispose ( ) ;
972978 return finalTarget ;
973979 }
974980
@@ -977,14 +983,31 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
977983 }
978984 private Prediction ? HandleStickyAim ( Prediction ? bestCandidate , List < Prediction > KDPredictions )
979985 {
980- bool stickyAimEnabled = Dictionary . toggleState [ "Sticky Aim" ] ;
981- if ( ! stickyAimEnabled )
986+ if ( ! Dictionary . toggleState [ "Sticky Aim" ] )
982987 {
983988 _currentTarget = bestCandidate ; // update anyway
984989 return bestCandidate ;
985990 }
986991
987- float thresholdSqr = ( float ) Math . Pow ( Dictionary . sliderSettings [ "Sticky Aim Threshold" ] , 2 ) ;
992+ float threshold = ( float ) Dictionary . sliderSettings [ "Sticky Aim Threshold" ] ;
993+ float thresholdSqr = threshold * threshold ;
994+
995+ if ( bestCandidate == null || KDPredictions == null || KDPredictions . Count == 0 )
996+ {
997+ if ( _currentTarget != null )
998+ {
999+ if ( ++ _consecutiveFramesWithoutTarget > MAX_FRAMES_WITHOUT_TARGET )
1000+ {
1001+ _currentTarget = null ;
1002+ _consecutiveFramesWithoutTarget = 0 ;
1003+ }
1004+ // keep previous target while within grace period
1005+ return _currentTarget ;
1006+ }
1007+ return null ;
1008+ }
1009+ // reset consecutive frames since we have a target
1010+ _consecutiveFramesWithoutTarget = 0 ;
9881011
9891012 if ( _currentTarget != null )
9901013 {
@@ -1011,9 +1034,10 @@ private void HandlePredictions(KalmanPrediction kalmanPrediction, Prediction clo
10111034 if ( ++ _consecutiveFramesWithoutTarget > MAX_FRAMES_WITHOUT_TARGET )
10121035 {
10131036 _currentTarget = null ;
1014- } else
1037+ }
1038+ else
10151039 {
1016- return null ; // No match found, keep the current target
1040+ return _currentTarget ;
10171041 }
10181042 }
10191043
@@ -1033,7 +1057,7 @@ private void UpdateDetectionBox(Prediction target, Rectangle detectionBox)
10331057 CenterYTranslated = target . CenterYTranslated ;
10341058 }
10351059 private ( List < double [ ] > , List < Prediction > ) PrepareKDTreeData (
1036- Tensor < float > outputTensor ,
1060+ Tensor < float > outputTensor ,
10371061 Rectangle detectionBox ,
10381062 float fovMinX , float fovMaxX , float fovMinY , float fovMaxY )
10391063 {
@@ -1160,14 +1184,15 @@ public void Dispose()
11601184
11611185 // Stop the loop
11621186 _isAiLoopRunning = false ;
1163- if ( _aiLoopThread != null && _aiLoopThread . IsAlive )
1187+ try
11641188 {
1165- if ( ! _aiLoopThread . Join ( TimeSpan . FromSeconds ( 1 ) ) )
1189+ _cts ? . Cancel ( ) ;
1190+ if ( _aiLoopTask != null )
11661191 {
1167- try { _aiLoopThread . Interrupt ( ) ; }
1168- catch { }
1192+ _aiLoopTask . Wait ( TimeSpan . FromSeconds ( 1 ) ) ;
11691193 }
11701194 }
1195+ catch { }
11711196
11721197 // Print final benchmarks
11731198 PrintBenchmarks ( ) ;
@@ -1178,6 +1203,8 @@ public void Dispose()
11781203 // Clean up other resources
11791204 _reusableInputArray = null ;
11801205 _reusableInputs = null ;
1206+ _reusableTensor = null ;
1207+ _cts ? . Dispose ( ) ;
11811208 _onnxModel ? . Dispose ( ) ;
11821209 _modeloptions ? . Dispose ( ) ;
11831210 }
0 commit comments