Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- завершен спринт 24 марта - 2 апреля с опережением плана и закрытием задач раньше срока - расширена и стабилизирована система призов: добавлены и доработаны QR-tier конфиги, новые типы и модели данных, сохранение `qrCodes` в `prizes.json`, fallback-логика и синхронизация через `PrizeSystemManager`, `PrizeDatabase` и `LevelPrizeConfig` - переработан flow настройки QR-призов: добавлены `QRCodeSettingsModal` и связанный UI-код, разнесены короткий текст приза и полный текст сообщения, добавлены шаблоны форматирования, корректная подстановка текста в QR и в UI-модалках, а также сохранение конфигурации между запусками - доработан flow суперприза и текстовых настроек: добавлены fallback-правила показа, корректная обработка пустых строк и строк из одних пробелов, обновлены binders и показ текста в меню - реализована и обновлена отдельная настройка витрины: добавлена `PrizeStandSettingsModal`, обновлена навигация строк, логика выбора ячейки и порогов очков, циклический выбор ячеек, ускорение изменения значений и исправления для keyboard/gamepad UX - доработана игровая модалка витрины `PrizeStandModal`: добавлен вывод очков игрока, форматируемый текст счета, улучшено отображение ячеек и связанная логика выбора приза - исправлен подсчет очков и интеграция с prize flow: `ScoreSystem` переведен на событийное обновление текущего счета, добавлены `CurrentScore`, `CurrentScoreChanged` и корректный display score, витрина и связанные UI-элементы теперь получают актуальные очки, а не нулевые или несвоевременные preview-значения - переработаны модальные окна и навигация UI: улучшены `Modal`, `ModalManager`, `UIRowPointer`, `RulesModal`, `QRCodeModal`, `PrizeStandModal`, `LevelPrizeSettingsModal` и др., чтобы фокус, указатель и submit работали консистентно без мыши - обновлен win/prize UI: доработаны `WinBannerController`, `GameEndUIController`, `MainMenu`, `QRCodeUIController`, `TopPanelTextController` и настройки UI, улучшены тексты баннеров, отображение призов и итогового счета - обновлены сцены и ассеты под новый flow: `MainMenu`, `Level UI`, prefab'ы строк настроек и кнопок, глобальные префабы, шрифты TMP, иконки, recovery-данные и ресурсы UI - добавлены вспомогательные UI-компоненты и служебные скрипты: `ButtonTextFocusColor`, `SelectableDescriptionTarget`, `QRTier` и другие изменения, поддерживающие новый конфигурируемый prize UI
402 lines
13 KiB
C#
402 lines
13 KiB
C#
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 ComHub
|
||
{
|
||
private readonly Dictionary<string, ComDeviceClient> _devices = new();
|
||
private readonly Dictionary<string, IComControllerHandler> _handlers = new();
|
||
private readonly ConcurrentQueue<string> _coinQueue = new();
|
||
private readonly ConcurrentQueue<DeviceInputMessage> _inputQueue = new();
|
||
private readonly ConcurrentQueue<string> _authorizedQueue = new();
|
||
private readonly ConcurrentQueue<DeviceDisconnectMessage> _disconnectedQueue = 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 readonly Func<string, string, IComControllerHandler> _handlerFactory;
|
||
|
||
private string _defaultDeviceId = string.Empty;
|
||
|
||
public ComHub(
|
||
bool useAllDetectedPorts,
|
||
string[] explicitPorts,
|
||
string defaultDevicePort,
|
||
bool enableEncryptedHandshake,
|
||
int handshakeTimeoutMs,
|
||
bool keepAlive,
|
||
string roleRequestCommand,
|
||
string roleResponsePrefix,
|
||
int roleRequestTimeoutMs,
|
||
string mainControllerRole,
|
||
Func<string, string, IComControllerHandler> handlerFactory)
|
||
{
|
||
_useAllDetectedPorts = useAllDetectedPorts;
|
||
_explicitPorts = explicitPorts ?? Array.Empty<string>();
|
||
_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;
|
||
_handlerFactory = handlerFactory;
|
||
}
|
||
|
||
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("ComHub", "Не найдено 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);
|
||
}
|
||
|
||
foreach (IComControllerHandler handler in _handlers.Values)
|
||
{
|
||
handler.Dispose();
|
||
}
|
||
|
||
_devices.Clear();
|
||
_handlers.Clear();
|
||
_defaultDeviceId = string.Empty;
|
||
|
||
while (_coinQueue.TryDequeue(out _))
|
||
{
|
||
}
|
||
|
||
while (_inputQueue.TryDequeue(out _))
|
||
{
|
||
}
|
||
|
||
while (_authorizedQueue.TryDequeue(out _))
|
||
{
|
||
}
|
||
|
||
while (_disconnectedQueue.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 bool TryDequeueDisconnected(out DeviceDisconnectMessage message) => _disconnectedQueue.TryDequeue(out message);
|
||
|
||
public string GetDeviceRole(string deviceId)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(deviceId))
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
return _devices.TryGetValue(deviceId, out ComDeviceClient client)
|
||
? client.ControllerRole
|
||
: string.Empty;
|
||
}
|
||
|
||
public string GetDeviceMac(string deviceId)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(deviceId))
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
return _devices.TryGetValue(deviceId, out ComDeviceClient client)
|
||
? client.MacAddress
|
||
: string.Empty;
|
||
}
|
||
|
||
public PrizeStandController GetPrizeStandController(string deviceId = null)
|
||
{
|
||
// Делегируем поиск витрины в ComSystemManager
|
||
return ComSystemManager.GetPrizeStandController(deviceId);
|
||
}
|
||
|
||
private string[] SelectPorts(string[] allPorts)
|
||
{
|
||
if (_useAllDetectedPorts)
|
||
{
|
||
return allPorts;
|
||
}
|
||
|
||
if (_explicitPorts.Length == 0)
|
||
{
|
||
return Array.Empty<string>();
|
||
}
|
||
|
||
HashSet<string> explicitSet = new(_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(
|
||
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.RawLineReceived += OnDeviceRawLineReceived;
|
||
client.Authorized += OnDeviceAuthorized;
|
||
client.Disconnected += OnDeviceDisconnected;
|
||
client.ConnectionLost += OnDeviceConnectionLost;
|
||
}
|
||
|
||
private void UnsubscribeDevice(ComDeviceClient client)
|
||
{
|
||
client.CoinAccepted -= OnDeviceCoinAccepted;
|
||
client.InputReceived -= OnDeviceInputReceived;
|
||
client.RawLineReceived -= OnDeviceRawLineReceived;
|
||
client.Authorized -= OnDeviceAuthorized;
|
||
client.Disconnected -= OnDeviceDisconnected;
|
||
client.ConnectionLost -= OnDeviceConnectionLost;
|
||
}
|
||
|
||
private void OnDeviceCoinAccepted(string deviceId)
|
||
{
|
||
if (_handlers.TryGetValue(deviceId, out IComControllerHandler handler))
|
||
{
|
||
handler.HandleCoinAccepted();
|
||
return;
|
||
}
|
||
|
||
_coinQueue.Enqueue(deviceId);
|
||
}
|
||
|
||
private void OnDeviceInputReceived(string deviceId, string inputBits)
|
||
{
|
||
if (_handlers.TryGetValue(deviceId, out IComControllerHandler handler))
|
||
{
|
||
handler.HandleInputReceived(inputBits);
|
||
return;
|
||
}
|
||
|
||
_inputQueue.Enqueue(new DeviceInputMessage(deviceId, inputBits));
|
||
}
|
||
|
||
private void OnDeviceRawLineReceived(string deviceId, string line)
|
||
{
|
||
if (_handlers.TryGetValue(deviceId, out IComControllerHandler handler))
|
||
{
|
||
handler.HandleRawLine(line);
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
if (_handlers.TryGetValue(deviceId, out IComControllerHandler existing))
|
||
{
|
||
existing.Dispose();
|
||
}
|
||
|
||
_handlers[deviceId] = CreateHandlerForDevice(deviceId);
|
||
_authorizedQueue.Enqueue(deviceId);
|
||
}
|
||
|
||
private void OnDeviceDisconnected(string deviceId)
|
||
{
|
||
if (!_devices.TryGetValue(deviceId, out ComDeviceClient client))
|
||
{
|
||
return;
|
||
}
|
||
|
||
string role = client.ControllerRole;
|
||
string mac = client.MacAddress;
|
||
|
||
UnsubscribeDevice(client);
|
||
_devices.Remove(deviceId);
|
||
|
||
if (_handlers.TryGetValue(deviceId, out IComControllerHandler handler))
|
||
{
|
||
handler.Dispose();
|
||
_handlers.Remove(deviceId);
|
||
}
|
||
|
||
if (_defaultDeviceId == deviceId)
|
||
{
|
||
_defaultDeviceId = _devices.Keys.FirstOrDefault() ?? string.Empty;
|
||
}
|
||
|
||
_disconnectedQueue.Enqueue(new DeviceDisconnectMessage(deviceId, role, mac));
|
||
}
|
||
|
||
private void OnDeviceConnectionLost(string deviceId)
|
||
{
|
||
string role = string.Empty;
|
||
string mac = string.Empty;
|
||
|
||
if (_devices.TryGetValue(deviceId, out ComDeviceClient client))
|
||
{
|
||
role = client.ControllerRole;
|
||
mac = client.MacAddress;
|
||
}
|
||
|
||
_disconnectedQueue.Enqueue(new DeviceDisconnectMessage(deviceId, role, mac));
|
||
}
|
||
|
||
private IComControllerHandler CreateHandlerForDevice(string deviceId)
|
||
{
|
||
string role = GetDeviceRole(deviceId);
|
||
IComControllerHandler customHandler = _handlerFactory?.Invoke(deviceId, role);
|
||
if (customHandler != null)
|
||
{
|
||
return customHandler;
|
||
}
|
||
|
||
return new DefaultComControllerHandler(deviceId, _coinQueue, _inputQueue);
|
||
}
|
||
}
|
||
|
||
internal readonly struct DeviceInputMessage
|
||
{
|
||
public readonly string DeviceId;
|
||
public readonly string InputBits;
|
||
|
||
public DeviceInputMessage(string deviceId, string inputBits)
|
||
{
|
||
DeviceId = deviceId;
|
||
InputBits = inputBits;
|
||
}
|
||
}
|
||
|
||
internal readonly struct DeviceDisconnectMessage
|
||
{
|
||
public readonly string DeviceId;
|
||
public readonly string Role;
|
||
public readonly string Mac;
|
||
|
||
public DeviceDisconnectMessage(string deviceId, string role, string mac)
|
||
{
|
||
DeviceId = deviceId;
|
||
Role = role ?? string.Empty;
|
||
Mac = mac ?? string.Empty;
|
||
}
|
||
}
|
||
}
|
||
|
||
|