using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using RR.Game2D.Core.Managers; namespace RR.Common.Controls { internal sealed class ComMultiManager { private readonly Dictionary _devices = new(); private readonly ConcurrentQueue _coinQueue = new(); private readonly ConcurrentQueue _inputQueue = new(); private readonly ConcurrentQueue _authorizedQueue = new(); private readonly bool _useAllDetectedPorts; private readonly string[] _explicitPorts; private readonly string _defaultDevicePort; private readonly bool _enableEncryptedHandshake; private readonly int _handshakeTimeoutMs; private readonly bool _keepAlive; private readonly string _roleRequestCommand; private readonly string _roleResponsePrefix; private readonly int _roleRequestTimeoutMs; private readonly string _mainControllerRole; private string _defaultDeviceId = string.Empty; public ComMultiManager( bool useAllDetectedPorts, string[] explicitPorts, string defaultDevicePort, bool enableEncryptedHandshake, int handshakeTimeoutMs, bool keepAlive, string roleRequestCommand, string roleResponsePrefix, int roleRequestTimeoutMs, string mainControllerRole) { _useAllDetectedPorts = useAllDetectedPorts; _explicitPorts = explicitPorts ?? Array.Empty(); _defaultDevicePort = defaultDevicePort ?? string.Empty; _enableEncryptedHandshake = enableEncryptedHandshake; _handshakeTimeoutMs = handshakeTimeoutMs; _keepAlive = keepAlive; _roleRequestCommand = roleRequestCommand ?? string.Empty; _roleResponsePrefix = roleResponsePrefix ?? "ROLE"; _roleRequestTimeoutMs = roleRequestTimeoutMs; _mainControllerRole = string.IsNullOrWhiteSpace(mainControllerRole) ? "main_controller" : mainControllerRole; } public string DefaultDeviceId => _defaultDeviceId; public string[] ConnectedDeviceIds => _devices.Keys.ToArray(); public void InitializeDevices() { ComNativeWrapper.Serial_CloseAll(); string[] allPorts = ComNativeWrapper.ListPorts(); if (allPorts.Length == 0) { GameManager.Logger?.LogWarning("ComMultiManager", "Не найдено COM-портов"); return; } string[] selectedPorts = SelectPorts(allPorts); foreach (string port in selectedPorts) { TryCreateDevice(port); } ResolveDefaultDeviceId(); } public void ShutdownAll(bool sendShutdownCommands) { foreach (ComDeviceClient client in _devices.Values) { UnsubscribeDevice(client); client.Stop(sendShutdownCommands); } _devices.Clear(); _defaultDeviceId = string.Empty; while (_coinQueue.TryDequeue(out _)) { } while (_inputQueue.TryDequeue(out _)) { } while (_authorizedQueue.TryDequeue(out _)) { } } public bool SendToDefault(string message) { if (string.IsNullOrEmpty(_defaultDeviceId)) return false; return SendToDevice(_defaultDeviceId, message); } public bool SendToDevice(string deviceId, string message) { if (string.IsNullOrWhiteSpace(deviceId)) return false; if (!_devices.TryGetValue(deviceId, out ComDeviceClient client)) return false; client.Send(message); return true; } public void SendToAll(string message) { foreach (ComDeviceClient client in _devices.Values) { client.Send(message); } } public string GetLastInputState(string deviceId) { if (string.IsNullOrWhiteSpace(deviceId)) return "11111111111111111111111111"; return _devices.TryGetValue(deviceId, out ComDeviceClient client) ? client.LastInputBits : "11111111111111111111111111"; } public bool TryDequeueCoin(out string deviceId) => _coinQueue.TryDequeue(out deviceId); public bool TryDequeueInput(out DeviceInputMessage message) => _inputQueue.TryDequeue(out message); public bool TryDequeueAuthorized(out string deviceId) => _authorizedQueue.TryDequeue(out deviceId); public string GetDeviceRole(string deviceId) { if (string.IsNullOrWhiteSpace(deviceId)) return string.Empty; return _devices.TryGetValue(deviceId, out ComDeviceClient client) ? client.ControllerRole : string.Empty; } private string[] SelectPorts(string[] allPorts) { if (_useAllDetectedPorts) return allPorts; if (_explicitPorts.Length == 0) return Array.Empty(); HashSet explicitSet = new HashSet(_explicitPorts.Where(p => !string.IsNullOrWhiteSpace(p))); return allPorts.Where(explicitSet.Contains).ToArray(); } private void ResolveDefaultDeviceId() { if (!string.IsNullOrWhiteSpace(_defaultDevicePort) && _devices.ContainsKey(_defaultDevicePort)) { _defaultDeviceId = _defaultDevicePort; return; } _defaultDeviceId = _devices.Keys.FirstOrDefault() ?? string.Empty; } private void TryCreateDevice(string port) { ComDeviceClient client = new ComDeviceClient( port, _enableEncryptedHandshake, _handshakeTimeoutMs, _keepAlive, _roleRequestCommand, _roleResponsePrefix, _roleRequestTimeoutMs); SubscribeDevice(client); if (!client.Start()) { UnsubscribeDevice(client); return; } _devices[client.DeviceId] = client; } private void SubscribeDevice(ComDeviceClient client) { client.CoinAccepted += OnDeviceCoinAccepted; client.InputReceived += OnDeviceInputReceived; client.Authorized += OnDeviceAuthorized; client.Disconnected += OnDeviceDisconnected; } private void UnsubscribeDevice(ComDeviceClient client) { client.CoinAccepted -= OnDeviceCoinAccepted; client.InputReceived -= OnDeviceInputReceived; client.Authorized -= OnDeviceAuthorized; client.Disconnected -= OnDeviceDisconnected; } private void OnDeviceCoinAccepted(string deviceId) { _coinQueue.Enqueue(deviceId); } private void OnDeviceInputReceived(string deviceId, string inputBits) { _inputQueue.Enqueue(new DeviceInputMessage(deviceId, inputBits)); } private void OnDeviceAuthorized(string deviceId) { if (_devices.TryGetValue(deviceId, out ComDeviceClient client) && string.Equals(client.ControllerRole, _mainControllerRole, StringComparison.OrdinalIgnoreCase)) { _defaultDeviceId = deviceId; } else if (string.IsNullOrWhiteSpace(_defaultDeviceId)) { _defaultDeviceId = deviceId; } _authorizedQueue.Enqueue(deviceId); } private void OnDeviceDisconnected(string deviceId) { if (!_devices.TryGetValue(deviceId, out ComDeviceClient client)) return; UnsubscribeDevice(client); _devices.Remove(deviceId); if (_defaultDeviceId == deviceId) { _defaultDeviceId = _devices.Keys.FirstOrDefault() ?? string.Empty; } } } internal readonly struct DeviceInputMessage { public readonly string DeviceId; public readonly string InputBits; public DeviceInputMessage(string deviceId, string inputBits) { DeviceId = deviceId; InputBits = inputBits; } } }