Рефакторинг ComWrapper в процессе

This commit is contained in:
_sega 2026-02-20 10:56:08 +03:00
parent f06c484c91
commit 0e9607f728
6 changed files with 215 additions and 313 deletions

View File

@ -11349,7 +11349,7 @@ Material:
- _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
- _EnvMatrixRotation: {r: 0, g: 0, b: 0, a: 0}
- _FaceColor: {r: 1, g: 1, b: 1, a: 1}
- _GlowColor: {r: 1, g: 0, b: 0.175066, a: 1}
- _GlowColor: {r: 0.91854715, g: 0, b: 1, a: 1}
- _MaskCoord: {r: 0, g: 0, b: 32767, a: 32767}
- _OutlineColor: {r: 0.554717, g: 0.554717, b: 0.554717, a: 1}
- _ReflectFaceColor: {r: 0, g: 0, b: 0, a: 1}

View File

@ -5978,7 +5978,6 @@ GameObject:
- component: {fileID: 259182289}
- component: {fileID: 259182279}
- component: {fileID: 259182281}
- component: {fileID: 259182282}
- component: {fileID: 259182283}
- component: {fileID: 259182284}
- component: {fileID: 259182286}
@ -6112,21 +6111,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 644deb7c42b828041920ca8c69f133ad, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &259182282
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 259182278}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f59acf8ffd30b44bbd96b3f99fb9c0a, type: 3}
m_Name:
m_EditorClassIdentifier:
saveMoneyBetweenSessions: 1
enablePrizeChecking: 1
keepAlive: 1
--- !u!114 &259182283
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@ -9,7 +9,7 @@ public class CheckLeadersFromMenuState : IState
public void enter()
{
ComWrapper.onInputRecieved.AddListener(ResetTimer);
ComWrapper.InputRecieved.AddListener(ResetTimer);
lb = GameManager.instance.leaderboard;
@ -24,7 +24,7 @@ public class CheckLeadersFromMenuState : IState
public void exit()
{
ComWrapper.onInputRecieved.RemoveListener(ResetTimer);
ComWrapper.InputRecieved.RemoveListener(ResetTimer);
lb.ClearRowsText();
lb.gameObject.SetActive(false);
SoundManager.instance.playSwitch();

View File

@ -452,7 +452,7 @@ public class GameManager : MonoBehaviour
SettingsManager.Initialize();
GetComponent<Logger>().Initialize();
GetComponent<ComWrapper>().Initialize();
ComWrapper.Initialize();
SingleWallet.Initialize();

View File

@ -38,7 +38,7 @@ public class COMinput : InputSource
LoadPinMappingConfiguration();
//ОБЯЗАТЕЛЬНЫЙ БЛОК
ComWrapper.onInputRecieved.AddListener(UpdateInputState);
ComWrapper.InputRecieved.AddListener(UpdateInputState);
}
catch (Exception e)

View File

@ -1,43 +1,41 @@
using System;
using System.Collections;
//Рефакторинг и перевод на асинхронку ComWrapper'a
//Надо потом еще логирование нормальное прикрутить и логгер сделать
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
public class ComWrapper : MonoBehaviour
public static class ComWrapper
{
//Настраиваем из инспектора
[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
[SerializeField] bool keepAlive = true;
public static UnityEvent PaymentReceived;
public static UnityEvent<string> onInputRecieved;
//То, что нужно вынести в конфиг
//[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
//[SerializeField] bool keepAlive = true;
static bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true. Да и что то не вижу реализации. Возможно оно уже где-то в ComInput
static bool keepAlive = true;
private const string REQUEST_INPUT = "91";
public static UnityEvent PaymentReceived;
public static event Action<string> InputRecieved;
private const string REQUEST_INPUT_COMMAND = "91";
private static int h;//идентификатор хэндла
private static byte[] buf = new byte[1024]; //Байт-буфер куда будут приходить данные с порта
bool boardEncrypted = false;
string encString;
string confirmString;
Coroutine keepAliveCoroutine;
private static ILogger logger;
//async
private CancellationTokenSource pollCancellationTokenSource;
//===========================start: PUBLIC API===========================//
public void Initialize()
private static CancellationTokenSource poll_cts, keepAlive_cts;
public static void Initialize()//Добавить логгер в конструктор
{
onInputRecieved = new UnityEvent<string>();
onInputRecieved?.AddListener(InputRecievedCather);
StartCoroutine(SetupBoard());
//Тут надо с конфига подтянуть настройки!
PaymentReceived = new UnityEvent();
OpenPort();
SetupBoardAsync();
}
public static void Send(string message)
@ -46,45 +44,163 @@ public class ComWrapper : MonoBehaviour
{
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
}
catch { Debug.LogError("Ошибка отправки строкового сигнала."); }
}
//===========================end: PUBLIC API===========================//
private void OnDisable()
{
StopAsyncPolling();
ClosePort();
catch { logger?.Log("Ошибка отправки строкового сигнала."); }
}
private void OnApplicationQuit()
private static async Task SetupBoardAsync()
{
bool auth = await BoardAuthAsync();
if(!auth) (new GameObject()).AddComponent(typeof(VfxPrefab));//Запускаем отвал
poll_cts = new CancellationTokenSource();
keepAlive_cts = new CancellationTokenSource();
StartPollingAsync();
StartKeepAliveProcessAsync(keepAlive_cts.Token);
}
private static void OpenPort()
{
Log("Инициализация порта");
//ComNativeWrapper.Serial_CloseAll();//или все таки h?, Пока не удаляем, посмотрим как сработает
var ports = ComNativeWrapper.ListPorts();
if (ports.Length == 0) { Log("Нет портов, завершаю"); return; }
string port = ports[^1];
Log("Открываю порт: " + port);
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 4, 10);
Log($"handle: {h}");
if (h <= 0)
{
Log("Open failed: " + ComNativeWrapper.GetLastError());
return;
}
else
{
Log($"Открыл порт: {port}");
}
}
//прописать нормальные логи
private static async Task<bool> BoardAuthAsync()
{
bool boardEncrypted = true; //предполагаем что зашифрована
float timeout = 1;
var sw = new Stopwatch();
//Проверяем зашифрована или нет
sw.Start();
RequestInputs();
while (sw.Elapsed.TotalSeconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.StartsWith("INPUT"))
{
boardEncrypted = false;
break;
}
}
sw.Stop();
//Не шифрованная -> На выход, пользуемся.
if (!boardEncrypted)
return true;
//А тут возможно шифрованная
Log("Шифрованная плата");
//Запрашиваем шифрованную строку
string encryptedString = string.Empty;
Send("666");
sw.Start();
while (sw.Elapsed.Seconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.StartsWith("ENC:"))
{
//пишем сразу без суффикса
encryptedString = entry.Substring(4);
break;
}
}
sw.Stop();
//Шифрованная строка не пришла -> идем нафиг
if (string.IsNullOrEmpty(encryptedString))
{
Log($"Зашифрованная строка не пришла");
return false;
}
//Раз пришла, надо расшифровывать
Log($"Расшифровываю {encryptedString}");
string decryptedString = BoardUtility.DecryptSerial(encryptedString);
//ждем подтверждение от платы
string confirmationString = string.Empty;
sw.Start();
Send("777" + decryptedString);
while (sw.Elapsed.Seconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.IndexOf("OK") != -1)
{
//Успех, плату активировали
sw.Stop();
return true;
}
}
//А вот тут мы не дождались подтверждения. Плохо, будем ломать игру.
Application.Quit();
return false;
}
private static void OnApplicationQuit()
{
StopAsyncPolling();
StopKeepAlive();
ClosePort();
}
private void StopAsyncPolling()
private static void StopAsyncPolling()
{
pollCancellationTokenSource?.Cancel();
pollCancellationTokenSource?.Dispose();
pollCancellationTokenSource = null;
poll_cts?.Cancel();
poll_cts?.Dispose();
poll_cts = null;
}
private async void StartAsyncPolling()
private static void StopKeepAlive()
{
keepAlive_cts?.Cancel();
keepAlive_cts?.Dispose();
keepAlive_cts = null;
}
//прописать норм логи
private static async void StartPollingAsync()
{
try
{
Logger.Instance.Log("Запуск асинхронного опроса порта");
Log("Запуск асинхронного опроса порта");
while (pollCancellationTokenSource != null &&
!pollCancellationTokenSource.Token.IsCancellationRequested)
while (poll_cts != null && !poll_cts.Token.IsCancellationRequested)
{
await Task.Delay(10, pollCancellationTokenSource.Token);
await Task.Delay(10, poll_cts.Token);
string comInput = await ReadBufferLineAsync(pollCancellationTokenSource.Token);
string comInput = await ReadBufferLineAsync(poll_cts.Token);
if (!string.IsNullOrEmpty(comInput))
{
// Прямой вызов - мы уже в главном потоке после await
@ -94,90 +210,35 @@ public class ComWrapper : MonoBehaviour
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Асинхронный опрос порта остановлен");
Log("Асинхронный опрос порта остановлен");
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка асинхронного опроса: {e.Message}");
Log($"Ошибка асинхронного опроса: {e.Message}");
}
}
private void ProcessComInputImmediate(string comInput)
//Логи
private static void ProcessComInputImmediate(string comInput)
{
Debug.LogError("COM: " + comInput);
Log($"COM. h={h}: {comInput}");
if (comInput == "COIN") PaymentReceived?.Invoke();
if (comInput.StartsWith("INPUT")) onInputRecieved?.Invoke(comInput.Substring(5));
if (comInput.StartsWith("ENC:")) encString = comInput;
if (comInput.StartsWith("ACTIVATED:")) confirmString = comInput;
if (comInput == "COIN")
PaymentReceived?.Invoke();
else if (comInput.StartsWith("INPUT"))
InputRecieved?.Invoke(comInput.Substring(5));
}
private void ToggleInputRecieved(int value) => PlayerPrefs.SetInt("inputToggle", value);
private int GetInputToggle() => PlayerPrefs.GetInt("inputToggle", 0);
private void InputRecievedCather(string input)
{
if (!string.IsNullOrEmpty(input))
{
//пришел инпут
ToggleInputRecieved(1);
}
}
#region Приватные методы
private void initPort()
{
Logger.Instance.Log("Инициализация порта");
ComNativeWrapper.Serial_CloseAll();//или все таки h?
//ComNativeWrapper.Serial_Close(h);
var ports = ComNativeWrapper.ListPorts();
if (ports.Length == 0) { Debug.LogError("Нет портов, завершаю"); return; }
string port = ports[^1];
Logger.Instance.Log("Открываю порт: " + port);
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 4, 10);
Logger.Instance.Log($"handle: {h}");
if (h <= 0)
{
Logger.Instance.Log("Open failed: " + ComNativeWrapper.GetLastError());
return;
}
else
{
Logger.Instance.Log($"Открыл порт: {port}");
}
}
#region Private Methods
private static void ClosePort()
{
Logger.Instance.Log($"Отпускаю порт handle:{h}");
Log($"Отпускаю порт handle:{h}");
if (h > 0)
{
ComNativeWrapper.Serial_Close(h);
h = 0;
}
}
private static string ReadBufferLine()
{
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
if (read > 0)
{
var s = Encoding.UTF8.GetString(buf, 0, read);
return s;
}
else if (read < 0)
{
//DebugWindow.Message($"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
}
return "";
}
private static async Task<string> ReadBufferLineAsync(CancellationToken cancellationToken = default)
{
try
@ -198,216 +259,73 @@ public class ComWrapper : MonoBehaviour
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Чтение порта отменено");
Log("Чтение порта отменено");
return string.Empty;
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка асинхронного чтения: {e.Message}");
Log($"Ошибка асинхронного чтения: {e.Message}");
return string.Empty;
}
}
private void requestInputs() => Send(REQUEST_INPUT);
private void pollPort()
{
//Тут все просто, читаем что нам приходит с порта и сравниваем.
string comInput = ReadBufferLine();
if (comInput != "")
{
Debug.Log("COM: " + comInput);
if (comInput == "COIN") SingleWallet.AddCredit();
if (comInput.StartsWith("INPUT")) onInputRecieved?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
if (comInput.StartsWith("ENC:")) encString = comInput;
if (comInput.StartsWith("ACTIVATED:")) confirmString = comInput;
}
}
private IEnumerator SetupBoard()
{
if (keepAliveCoroutine != null) StopCoroutine(keepAliveCoroutine);
initPort();
boardEncrypted = true; //предполагаем что зашифрована
//пробуем получить инпуты. Вдруг плата осталась расшифрована
ToggleInputRecieved(0);
requestInputs();
float timer = 1;
while (GetInputToggle() == 0 && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
//Есть инпут. незашифровано
if (GetInputToggle() == 1)
{
boardEncrypted = false;
}
private static void RequestInputs()
=> Send(REQUEST_INPUT_COMMAND);
if (boardEncrypted)
{
Logger.Instance.Log("Шифрованная плата");
//запрос
encString = "";
Send("666");
//ждем пока придет ENC:
timer = 1;
while (string.IsNullOrEmpty(encString) && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
//получили ENC: строку или вышли по таймеру
//Строка пришла
if (!string.IsNullOrEmpty(encString))
{
Logger.Instance.Log($"Расшифровываю {encString}");
string decr = BoardUtility.DecryptSerial(ExtractData(encString));
//подтверждение
confirmString = "";
Send("777" + decr);
}
else
{
Logger.Instance.Log($"Зашифрованная строка не пришла"); //отвал игры пускать
(new GameObject()).AddComponent(typeof(VfxPrefab));
}
//ждем подтверждение от платы
timer = 2;
while (string.IsNullOrEmpty(confirmString) && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
if (string.IsNullOrEmpty(confirmString))
{
Application.Quit();
}
else
{
if (confirmString.IndexOf("OK") != -1)
{
Logger.Instance.Log("Активировано.");
}
else
{
Application.Quit();
}
}
}
else
{
Logger.Instance.Log("НЕшифрованная плата");
}
//async
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
/* if (keepAlive)
keepAliveCoroutine = StartCoroutine(KeepPortAliveProcess());*/
//async
pollCancellationTokenSource = new CancellationTokenSource();
StartAsyncPolling();
StartKeepAliveProcess();
}
private async void StartKeepAliveProcess()
{
if (keepAlive)
{
await StartKeepAliveProcessAsync(pollCancellationTokenSource.Token);
}
}
private async Task StartKeepAliveProcessAsync(CancellationToken cancellationToken)
//Сделать ожидание простоя около минуты на проверку и переподелючение.
private static async Task StartKeepAliveProcessAsync(CancellationToken cancellationToken)
{
try
{
Logger.Instance.Log("Запуск асинхронной проверки порта");
Log("Запуск асинхронной проверки порта");
while (!cancellationToken.IsCancellationRequested)
{
await SendRequestInputsAsync(cancellationToken);
await Task.Delay(2000, cancellationToken); // 2 секунды
if (!await CheckPortConnectionAsync(cancellationToken))
if (!await CheckPortConnectionAsync())
{
Logger.Instance.Log("Плата отвалилась. Запускаю переподключение.");
Log("Плата отвалилась. Запускаю переподключение.");
await ReinitializePortAsync(cancellationToken);
}
else
{
ToggleInputRecieved(0);
}
await Task.Delay(10000, cancellationToken); // 10 секунд
await Task.Delay(10000, cancellationToken); //Задержка
}
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Асинхронная проверка порта остановлена");
Log("Асинхронная проверка порта остановлена");
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка проверки порта: {e.Message}");
Log($"Ошибка проверки порта: {e.Message}");
}
}
private async Task SendRequestInputsAsync(CancellationToken cancellationToken)
private static async Task<bool> CheckPortConnectionAsync()
{
try
var sw = Stopwatch.StartNew();
float timeout = 1;
RequestInputs();
while (sw.Elapsed.Seconds < timeout)
{
requestInputs();
await Task.CompletedTask;
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка отправки запроса инпутов: {e.Message}");
string entry = await ReadBufferLineAsync();
if(!string.IsNullOrEmpty(entry) && entry.StartsWith("INPUT"))
{
return true;
}
}
sw.Stop();
return false;
}
private async Task<bool> CheckPortConnectionAsync(CancellationToken cancellationToken)
//реализовать
private static async Task ReinitializePortAsync(CancellationToken cancellationToken)
{
// Простая проверка, не требует Task.Run
return await Task.FromResult(GetInputToggle() != 0);
}
ClosePort();
await SetupBoardAsync();
}
private async Task ReinitializePortAsync(CancellationToken cancellationToken)
{
await Task.Run(() => initPort(), cancellationToken);
}
private static string ExtractData(string input)
{
const string encPrefix = "ENC:";
int encIndex = input.IndexOf(encPrefix);
if (encIndex == -1)
{
Logger.Instance.Log($"Неверный формат шифрованной строки {input}");
throw new ArgumentException("Invalid input format");
}
int dataStart = encIndex + encPrefix.Length;
return input.Substring(4);
}
private static void Log(string message) => logger?.Log($"CW{message}");
#endregion
}