diff --git a/Botbases/RSBot.Training/TrainingBase.cs b/Botbases/RSBot.Training/TrainingBase.cs index 748da902..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.CancelAction(); - Bundles.Stop(); } } diff --git a/Library/RSBot.Core/Bot.cs b/Library/RSBot.Core/Bot.cs index df4703c2..30dd06e3 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,31 +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) - 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; + } } /// @@ -82,27 +102,61 @@ public void Start() /// public void Stop() { - ScriptManager.Stop(); - ShoppingManager.Stop(); - PickupManager.Stop(); + CancellationTokenSource tokenSource; - if (Botbase == null) - return; + lock (_lock) + { + if (Botbase == null || !Running) + return; - if (!Running) - return; + Running = false; + tokenSource = TokenSource; + } - if (!TokenSource.IsCancellationRequested) - TokenSource.Cancel(); + if (tokenSource != null && !tokenSource.IsCancellationRequested) + tokenSource.Cancel(); EventManager.FireEvent("OnStopBot"); Log.Notify($"Stopping bot {Botbase.Name}"); + CancelActionOnStop(); + Game.SelectedEntity = null; + + ScriptManager.Stop(); + ShoppingManager.Stop(); + PickupManager.Stop(); Botbase.Stop(); - Running = false; Log.Notify($"Stopped bot {Botbase.Name}"); Log.Status("Bot stopped"); } + + private void CancelActionOnStop() + { + var player = Game.Player; + if (player == null || !player.InAction) + return; + + _ = CancelActionOnStopAsync(); + + async Task CancelActionOnStopAsync() + { + 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); + } + } + } } diff --git a/Library/RSBot.Core/Components/SkillManager.cs b/Library/RSBot.Core/Components/SkillManager.cs index 5f0f335b..145b732b 100644 --- a/Library/RSBot.Core/Components/SkillManager.cs +++ b/Library/RSBot.Core/Components/SkillManager.cs @@ -631,24 +631,42 @@ 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(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); - callback.AwaitResponse(); + PacketManager.SendPacket(CreateCancelActionPacket(), PacketDestination.Server, callback); + callback.AwaitResponse(timeout); return callback.IsCompleted; } + + private static Packet CreateCancelActionPacket() + { + var packet = new Packet(0x7074); + packet.WriteByte(0x02); //Cancel + + return packet; + } } diff --git a/Library/RSBot.Core/Network/Socket/Client.cs b/Library/RSBot.Core/Network/Socket/Client.cs index 60125290..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 { } } @@ -97,12 +93,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 +132,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..db3ae740 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. @@ -108,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; } /// @@ -126,19 +161,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 +179,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 +211,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 +239,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..765b5cef 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(); } /// @@ -173,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(); @@ -192,8 +190,7 @@ public void Disconnect() { _socket = null; OnDisconnected(); + IsClosing = false; } - - IsClosing = false; } }