From da1db2e9a75c52db5afe0895777ed963031eb778 Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:35:01 +0200 Subject: [PATCH 1/6] Reduced network polling and unblocked bot stop --- Botbases/RSBot.Training/TrainingBase.cs | 2 +- Library/RSBot.Core/Bot.cs | 16 +++-- Library/RSBot.Core/Components/SkillManager.cs | 10 ++- Library/RSBot.Core/Network/Socket/Client.cs | 3 +- Library/RSBot.Core/Network/Socket/NetBase.cs | 72 ++++++++++++++----- Library/RSBot.Core/Network/Socket/Server.cs | 2 + 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/Botbases/RSBot.Training/TrainingBase.cs b/Botbases/RSBot.Training/TrainingBase.cs index 748da902..0028374c 100644 --- a/Botbases/RSBot.Training/TrainingBase.cs +++ b/Botbases/RSBot.Training/TrainingBase.cs @@ -80,7 +80,7 @@ public void Stop() lock (Container.Lock) { if (Game.Player.InAction) - SkillManager.CancelAction(); + SkillManager.CancelAction(0); Bundles.Stop(); } diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index df4703c2..b7a3b2e2 100644 --- a/Library/RSBot.Core/Bot.cs +++ b/Library/RSBot.Core/Bot.cs @@ -66,7 +66,10 @@ public void Start() while (!TokenSource.IsCancellationRequested) { if (!Game.Ready) + { + await Task.Delay(100); continue; + } Botbase.Tick(); await Task.Delay(100); @@ -82,25 +85,26 @@ public void Start() /// public void Stop() { - ScriptManager.Stop(); - ShoppingManager.Stop(); - PickupManager.Stop(); - if (Botbase == null) return; if (!Running) return; - if (!TokenSource.IsCancellationRequested) + Running = false; + + if (TokenSource != null && !TokenSource.IsCancellationRequested) TokenSource.Cancel(); EventManager.FireEvent("OnStopBot"); Log.Notify($"Stopping bot {Botbase.Name}"); Game.SelectedEntity = null; + + ScriptManager.Stop(); + ShoppingManager.Stop(); + PickupManager.Stop(); Botbase.Stop(); - Running = false; Log.Notify($"Stopped bot {Botbase.Name}"); Log.Status("Bot stopped"); diff --git a/Library/RSBot.Core/Components/SkillManager.cs b/Library/RSBot.Core/Components/SkillManager.cs index 5f0f335b..643240c9 100644 --- a/Library/RSBot.Core/Components/SkillManager.cs +++ b/Library/RSBot.Core/Components/SkillManager.cs @@ -631,11 +631,17 @@ public static void CancelBuff(uint skillId) /// Cancels the action. /// /// - public static bool CancelAction() + public static bool CancelAction(int timeout = 500) { var packet = new Packet(0x7074); packet.WriteByte(0x02); //Cancel + if (timeout <= 0) + { + PacketManager.SendPacket(packet, PacketDestination.Server); + return true; + } + var callback = new AwaitCallback( response => { @@ -647,7 +653,7 @@ public static bool CancelAction() ); PacketManager.SendPacket(packet, PacketDestination.Server, callback); - callback.AwaitResponse(); + callback.AwaitResponse(timeout); return callback.IsCompleted; } diff --git a/Library/RSBot.Core/Network/Socket/Client.cs b/Library/RSBot.Core/Network/Socket/Client.cs index 60125290..b1e0be56 100644 --- a/Library/RSBot.Core/Network/Socket/Client.cs +++ b/Library/RSBot.Core/Network/Socket/Client.cs @@ -97,12 +97,12 @@ private void OnClientConnect(IAsyncResult ar) if (IsClosing) return; - EnablePacketDispatcher = true; _socket = _listener.EndAccept(ar); _protocol = new SecurityProtocol(); _protocol.GenerateSecurity(true, true, true); + EnablePacketDispatcher = true; _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnBeginReceiveCallback, null); OnConnected(); @@ -136,6 +136,7 @@ private void OnBeginReceiveCallback(IAsyncResult ar) } _protocol.Recv(_buffer, 0, receivedSize); + SignalPacketDispatcher(); } catch (SocketException se) { diff --git a/Library/RSBot.Core/Network/Socket/NetBase.cs b/Library/RSBot.Core/Network/Socket/NetBase.cs index f308a4ce..f625360a 100644 --- a/Library/RSBot.Core/Network/Socket/NetBase.cs +++ b/Library/RSBot.Core/Network/Socket/NetBase.cs @@ -31,13 +31,25 @@ public class NetBase(bool isClient = false) /// private bool _isClient { get; set; } = isClient; + private readonly AutoResetEvent _packetSignal = new(false); + private volatile bool _enablePacketDispatcher; + private volatile bool _isClosing; + /// /// Gets or sets a value indicating whether this instance is closing. /// /// /// true if this instance is closing; otherwise, false. /// - public bool IsClosing { get; set; } + public bool IsClosing + { + get => _isClosing; + set + { + _isClosing = value; + _packetSignal.Set(); + } + } /// /// Gets or sets a value indicating whether [enable packet processor]. @@ -45,7 +57,15 @@ public class NetBase(bool isClient = false) /// /// true if [enable packet processor]; otherwise, false. /// - public bool EnablePacketDispatcher { get; set; } + public bool EnablePacketDispatcher + { + get => _enablePacketDispatcher; + set + { + _enablePacketDispatcher = value; + _packetSignal.Set(); + } + } /// /// Gets or sets the security protocol. @@ -126,19 +146,17 @@ private void ProcessPacketsThreaded() { try { - while (!EnablePacketDispatcher && !IsClosing) - Thread.Sleep(1); - - while (EnablePacketDispatcher && !IsClosing) + while (!IsClosing) { - ProcessQueuedPackets(); - Thread.Sleep(1); - } - - if (IsClosing) - return; + if (!EnablePacketDispatcher) + { + _packetSignal.WaitOne(100); + continue; + } - ProcessPacketsThreaded(); + if (!ProcessQueuedPackets()) + _packetSignal.WaitOne(15); + } } catch { } } @@ -146,18 +164,22 @@ private void ProcessPacketsThreaded() /// /// Processes the packets. /// - private void ProcessQueuedPackets() + private bool ProcessQueuedPackets() { try { if (IsClosing || !EnablePacketDispatcher) - return; + return false; + + var packets = _protocol?.TransferIncoming(); + var processed = false; - var packets = _protocol.TransferIncoming(); if (packets != null) { foreach (var packet in packets) { + processed = true; + if (packet.Opcode == 0x5000 || packet.Opcode == 0x9000) continue; @@ -174,17 +196,23 @@ private void ProcessQueuedPackets() var buffers = _protocol?.TransferOutgoing(); if (buffers == null) - return; + return processed; foreach (var buffer in buffers) { if (_socket == null || IsClosing || !EnablePacketDispatcher || !_socket.Connected) - return; + return processed; _socket.Send(buffer); + processed = true; } + + return processed; + } + catch + { + return false; } - catch { } } /// @@ -196,5 +224,11 @@ public void Send(Packet packet) OnPacketSent(packet); _protocol?.Send(packet); + _packetSignal.Set(); + } + + protected void SignalPacketDispatcher() + { + _packetSignal.Set(); } } diff --git a/Library/RSBot.Core/Network/Socket/Server.cs b/Library/RSBot.Core/Network/Socket/Server.cs index c39a91e6..ffdd92e3 100644 --- a/Library/RSBot.Core/Network/Socket/Server.cs +++ b/Library/RSBot.Core/Network/Socket/Server.cs @@ -133,6 +133,7 @@ private void OnBeginReceiveCallback(IAsyncResult ar) _receivedPostInitialHandshakePacket = true; _protocol.Recv(_buffer, 0, receivedSize); + SignalPacketDispatcher(); } catch (SocketException se) { @@ -166,6 +167,7 @@ public void Send(Packet packet) { OnPacketSent(packet); _protocol.Send(packet); + SignalPacketDispatcher(); } /// From d1b64816cfdb6c182e5762c74c610f12e6621811 Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:09:17 +0200 Subject: [PATCH 2/6] Harden bot stop and network worker lifecycle --- Botbases/RSBot.Training/TrainingBase.cs | 2 +- Library/RSBot.Core/Bot.cs | 82 ++++++++++++------- Library/RSBot.Core/Components/SkillManager.cs | 6 -- Library/RSBot.Core/Network/Socket/Client.cs | 6 +- Library/RSBot.Core/Network/Socket/NetBase.cs | 31 +++++-- Library/RSBot.Core/Network/Socket/Server.cs | 11 +-- 6 files changed, 79 insertions(+), 59 deletions(-) diff --git a/Botbases/RSBot.Training/TrainingBase.cs b/Botbases/RSBot.Training/TrainingBase.cs index 0028374c..89cbd1c9 100644 --- a/Botbases/RSBot.Training/TrainingBase.cs +++ b/Botbases/RSBot.Training/TrainingBase.cs @@ -80,7 +80,7 @@ public void Stop() lock (Container.Lock) { if (Game.Player.InAction) - SkillManager.CancelAction(0); + SkillManager.CancelAction(50); Bundles.Stop(); } diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index b7a3b2e2..c7dcfdc7 100644 --- a/Library/RSBot.Core/Bot.cs +++ b/Library/RSBot.Core/Bot.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using System; using RSBot.Core.Components; using RSBot.Core.Event; using RSBot.Core.Plugins; @@ -8,6 +9,9 @@ namespace RSBot.Core; public class Bot { + private readonly object _lock = new(); + private Task _workerTask; + /// /// Gets or sets a value indicating whether this is running. /// @@ -50,34 +54,47 @@ public void SetBotbaseView(IBotbaseView botBaseView) /// public void Start() { - if (Running || Botbase == null) - return; - - TokenSource = new CancellationTokenSource(); - - Task.Factory.StartNew( - async e => - { - Running = true; + CancellationTokenSource tokenSource; + + lock (_lock) + { + if (Running || Botbase == null || (_workerTask != null && !_workerTask.IsCompleted)) + return; + + tokenSource = new CancellationTokenSource(); + TokenSource = tokenSource; + Running = true; + _workerTask = Task.Run(() => RunAsync(tokenSource), tokenSource.Token); + } + } - EventManager.FireEvent("OnStartBot"); - Botbase.Start(); + private async Task RunAsync(CancellationTokenSource tokenSource) + { + var token = tokenSource.Token; - while (!TokenSource.IsCancellationRequested) - { - if (!Game.Ready) - { - await Task.Delay(100); - continue; - } + try + { + EventManager.FireEvent("OnStartBot"); + Botbase.Start(); + while (!token.IsCancellationRequested) + { + if (Game.Ready) Botbase.Tick(); - await Task.Delay(100); - } - }, - TokenSource.Token, - TaskCreationOptions.LongRunning - ); + + await Task.Delay(100, token); + } + } + catch (OperationCanceledException) when (token.IsCancellationRequested) { } + catch (Exception ex) + { + Log.Fatal(ex); + } + finally + { + if (ReferenceEquals(TokenSource, tokenSource)) + Running = false; + } } /// @@ -85,16 +102,19 @@ public void Start() /// public void Stop() { - if (Botbase == null) - return; + CancellationTokenSource tokenSource; - if (!Running) - return; + lock (_lock) + { + if (Botbase == null || !Running) + return; - Running = false; + Running = false; + tokenSource = TokenSource; + } - if (TokenSource != null && !TokenSource.IsCancellationRequested) - TokenSource.Cancel(); + if (tokenSource != null && !tokenSource.IsCancellationRequested) + tokenSource.Cancel(); EventManager.FireEvent("OnStopBot"); Log.Notify($"Stopping bot {Botbase.Name}"); diff --git a/Library/RSBot.Core/Components/SkillManager.cs b/Library/RSBot.Core/Components/SkillManager.cs index 643240c9..2314374c 100644 --- a/Library/RSBot.Core/Components/SkillManager.cs +++ b/Library/RSBot.Core/Components/SkillManager.cs @@ -636,12 +636,6 @@ public static bool CancelAction(int timeout = 500) var packet = new Packet(0x7074); packet.WriteByte(0x02); //Cancel - if (timeout <= 0) - { - PacketManager.SendPacket(packet, PacketDestination.Server); - return true; - } - var callback = new AwaitCallback( response => { diff --git a/Library/RSBot.Core/Network/Socket/Client.cs b/Library/RSBot.Core/Network/Socket/Client.cs index b1e0be56..5f337e5b 100644 --- a/Library/RSBot.Core/Network/Socket/Client.cs +++ b/Library/RSBot.Core/Network/Socket/Client.cs @@ -58,10 +58,7 @@ public void Shutdown() { try { - EnablePacketDispatcher = false; - IsClosing = true; - - _dispatcherThread?.Join(); + StopNetWorker(); //Close Socket if (_socket != null) @@ -81,7 +78,6 @@ public void Shutdown() } _protocol = null; - _dispatcherThread = null; } catch { } } diff --git a/Library/RSBot.Core/Network/Socket/NetBase.cs b/Library/RSBot.Core/Network/Socket/NetBase.cs index f625360a..db3ae740 100644 --- a/Library/RSBot.Core/Network/Socket/NetBase.cs +++ b/Library/RSBot.Core/Network/Socket/NetBase.cs @@ -128,15 +128,30 @@ public virtual void OnPacketSent(Packet packet) protected void StartNetWorker() { - if (_dispatcherThread == null) + if (_dispatcherThread != null && _dispatcherThread.IsAlive) + return; + + _dispatcherThread = new Thread(ProcessPacketsThreaded) { - _dispatcherThread = new Thread(ProcessPacketsThreaded) - { - Name = "Network.PacketProcessor", - IsBackground = true, - }; - _dispatcherThread.Start(); - } + Name = "Network.PacketProcessor", + IsBackground = true, + }; + _dispatcherThread.Start(); + } + + protected void StopNetWorker(int joinTimeout = 1000) + { + EnablePacketDispatcher = false; + IsClosing = true; + + var dispatcherThread = _dispatcherThread; + var stopped = dispatcherThread == null || !dispatcherThread.IsAlive || dispatcherThread == Thread.CurrentThread; + + if (dispatcherThread != null && dispatcherThread.IsAlive && dispatcherThread != Thread.CurrentThread) + stopped = dispatcherThread.Join(joinTimeout); + + if (stopped) + _dispatcherThread = null; } /// diff --git a/Library/RSBot.Core/Network/Socket/Server.cs b/Library/RSBot.Core/Network/Socket/Server.cs index ffdd92e3..765b5cef 100644 --- a/Library/RSBot.Core/Network/Socket/Server.cs +++ b/Library/RSBot.Core/Network/Socket/Server.cs @@ -175,15 +175,11 @@ public void Send(Packet packet) /// public void Disconnect() { - EnablePacketDispatcher = false; - IsClosing = true; + StopNetWorker(); try { - if (_socket == null) - return; - - if (_socket.Connected) + if (_socket != null && _socket.Connected) { _socket.Shutdown(SocketShutdown.Both); _socket.Close(); @@ -194,8 +190,7 @@ public void Disconnect() { _socket = null; OnDisconnected(); + IsClosing = false; } - - IsClosing = false; } } From c4285b948fba7b95393cabdb45c5fa02a2176aeb Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:26:25 +0200 Subject: [PATCH 3/6] Avoid blocking action cancel on bot stop --- Botbases/RSBot.Training/TrainingBase.cs | 2 +- Library/RSBot.Core/Components/SkillManager.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Botbases/RSBot.Training/TrainingBase.cs b/Botbases/RSBot.Training/TrainingBase.cs index 89cbd1c9..dc33a72d 100644 --- a/Botbases/RSBot.Training/TrainingBase.cs +++ b/Botbases/RSBot.Training/TrainingBase.cs @@ -80,7 +80,7 @@ public void Stop() lock (Container.Lock) { if (Game.Player.InAction) - SkillManager.CancelAction(50); + SkillManager.CancelActionNoWait(); Bundles.Stop(); } diff --git a/Library/RSBot.Core/Components/SkillManager.cs b/Library/RSBot.Core/Components/SkillManager.cs index 2314374c..8da7c40e 100644 --- a/Library/RSBot.Core/Components/SkillManager.cs +++ b/Library/RSBot.Core/Components/SkillManager.cs @@ -651,4 +651,12 @@ public static bool CancelAction(int timeout = 500) return callback.IsCompleted; } + + public static void CancelActionNoWait() + { + var packet = new Packet(0x7074); + packet.WriteByte(0x02); //Cancel + + PacketManager.SendPacket(packet, PacketDestination.Server); + } } From 09ee8811354c8395244a2f49db3e5ae36f6d8e86 Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:43:40 +0200 Subject: [PATCH 4/6] Handle recurring cancel-action responses on bot stop --- Botbases/RSBot.Training/TrainingBase.cs | 3 --- Library/RSBot.Core/Bot.cs | 20 ++++++++++++++ Library/RSBot.Core/Components/SkillManager.cs | 26 +++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Botbases/RSBot.Training/TrainingBase.cs b/Botbases/RSBot.Training/TrainingBase.cs index dc33a72d..91b6f20f 100644 --- a/Botbases/RSBot.Training/TrainingBase.cs +++ b/Botbases/RSBot.Training/TrainingBase.cs @@ -79,9 +79,6 @@ public void Stop() { lock (Container.Lock) { - if (Game.Player.InAction) - SkillManager.CancelActionNoWait(); - Bundles.Stop(); } } diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index c7dcfdc7..93ff0175 100644 --- a/Library/RSBot.Core/Bot.cs +++ b/Library/RSBot.Core/Bot.cs @@ -119,6 +119,8 @@ public void Stop() EventManager.FireEvent("OnStopBot"); Log.Notify($"Stopping bot {Botbase.Name}"); + CancelActionOnStop(); + Game.SelectedEntity = null; ScriptManager.Stop(); @@ -129,4 +131,22 @@ public void Stop() Log.Notify($"Stopped bot {Botbase.Name}"); Log.Status("Bot stopped"); } + + private void CancelActionOnStop() + { + SkillManager.CancelAction(0); + + _ = Task.Run(async () => + { + for (var i = 1; i < 5; i++) + { + await Task.Delay(100); + + if (Running || !Game.Player.InAction) + return; + + SkillManager.CancelAction(0); + } + }); + } } diff --git a/Library/RSBot.Core/Components/SkillManager.cs b/Library/RSBot.Core/Components/SkillManager.cs index 8da7c40e..145b732b 100644 --- a/Library/RSBot.Core/Components/SkillManager.cs +++ b/Library/RSBot.Core/Components/SkillManager.cs @@ -633,30 +633,40 @@ public static void CancelBuff(uint skillId) /// public static bool CancelAction(int timeout = 500) { - var packet = new Packet(0x7074); - packet.WriteByte(0x02); //Cancel + if (timeout <= 0) + { + PacketManager.SendPacket(CreateCancelActionPacket(), PacketDestination.Server); + return true; + } var callback = new AwaitCallback( response => { - return response.ReadByte() == 0x02 && response.ReadByte() == 0x00 - ? AwaitCallbackResult.Success - : AwaitCallbackResult.ConditionFailed; + var state = response.ReadByte(); + var recurring = response.ReadByte(); + + if (state == 0x02 && recurring == 0x00) + return AwaitCallbackResult.Success; + + if (state == 0x02) + return AwaitCallbackResult.Fail; + + return AwaitCallbackResult.ConditionFailed; }, 0xB074 ); - PacketManager.SendPacket(packet, PacketDestination.Server, callback); + PacketManager.SendPacket(CreateCancelActionPacket(), PacketDestination.Server, callback); callback.AwaitResponse(timeout); return callback.IsCompleted; } - public static void CancelActionNoWait() + private static Packet CreateCancelActionPacket() { var packet = new Packet(0x7074); packet.WriteByte(0x02); //Cancel - PacketManager.SendPacket(packet, PacketDestination.Server); + return packet; } } From 2ac7c06a199f04fcd4be8dd08637d510bebaf92e Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:56:19 +0200 Subject: [PATCH 5/6] Protection against NullReferenceException --- Library/RSBot.Core/Bot.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index 93ff0175..a12526a8 100644 --- a/Library/RSBot.Core/Bot.cs +++ b/Library/RSBot.Core/Bot.cs @@ -134,6 +134,10 @@ public void Stop() private void CancelActionOnStop() { + var player = Game.Player; + if (player == null) + return; + SkillManager.CancelAction(0); _ = Task.Run(async () => @@ -142,7 +146,7 @@ private void CancelActionOnStop() { await Task.Delay(100); - if (Running || !Game.Player.InAction) + if (Running || !Game.Ready || !ReferenceEquals(Game.Player, player) || !player.InAction) return; SkillManager.CancelAction(0); From a80600a5bd0320e84a7f59a56c54ef09911f3223 Mon Sep 17 00:00:00 2001 From: kis1yi <35934681+kis1yi@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:31:35 +0200 Subject: [PATCH 6/6] Improved cancel action retries --- Library/RSBot.Core/Bot.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index a12526a8..30dd06e3 100644 --- a/Library/RSBot.Core/Bot.cs +++ b/Library/RSBot.Core/Bot.cs @@ -135,22 +135,28 @@ public void Stop() private void CancelActionOnStop() { var player = Game.Player; - if (player == null) + if (player == null || !player.InAction) return; - SkillManager.CancelAction(0); + _ = CancelActionOnStopAsync(); - _ = Task.Run(async () => + async Task CancelActionOnStopAsync() { - for (var i = 1; i < 5; i++) - { - await Task.Delay(100); + const int attempts = 5; + const int retryDelay = 1000; + for (var i = 0; i < attempts; i++) + { if (Running || !Game.Ready || !ReferenceEquals(Game.Player, player) || !player.InAction) return; SkillManager.CancelAction(0); + + if (i == attempts - 1) + return; + + await Task.Delay(retryDelay).ConfigureAwait(false); } - }); + } } }