Рефакторинг ComWrapper(Готов к тестированию)
This commit is contained in:
parent
0e9607f728
commit
6296f9e4e1
@ -9,8 +9,7 @@ public class CheckLeadersFromMenuState : IState
|
||||
|
||||
public void enter()
|
||||
{
|
||||
ComWrapper.InputRecieved.AddListener(ResetTimer);
|
||||
|
||||
ComWrapper.InputRecieved +=ResetTimer;
|
||||
lb = GameManager.instance.leaderboard;
|
||||
|
||||
//вкл
|
||||
@ -24,7 +23,7 @@ public class CheckLeadersFromMenuState : IState
|
||||
|
||||
public void exit()
|
||||
{
|
||||
ComWrapper.InputRecieved.RemoveListener(ResetTimer);
|
||||
ComWrapper.InputRecieved-=ResetTimer;
|
||||
lb.ClearRowsText();
|
||||
lb.gameObject.SetActive(false);
|
||||
SoundManager.instance.playSwitch();
|
||||
|
||||
@ -456,7 +456,7 @@ public class GameManager : MonoBehaviour
|
||||
|
||||
SingleWallet.Initialize();
|
||||
|
||||
ComWrapper.PaymentReceived.AddListener(ProcessDifficultyCounter);
|
||||
ComWrapper.PaymentReceived += ProcessDifficultyCounter;
|
||||
SettingsManager.OnSettingsChanged.AddListener(ProcessDifficultyCounter);
|
||||
|
||||
soundManager.Initialize();
|
||||
|
||||
@ -38,7 +38,7 @@ public class COMinput : InputSource
|
||||
LoadPinMappingConfiguration();
|
||||
|
||||
//ОБЯЗАТЕЛЬНЫЙ БЛОК
|
||||
ComWrapper.InputRecieved.AddListener(UpdateInputState);
|
||||
ComWrapper.InputRecieved += UpdateInputState;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -82,7 +82,7 @@ public class COMinput : InputSource
|
||||
Debug.Log($"Используется версия 1 распиновки (значение по умолчанию). Полученная версия: {elektroVersion}");
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -1,71 +1,227 @@
|
||||
//Рефакторинг и перевод на асинхронку ComWrapper'a
|
||||
//Надо потом еще логирование нормальное прикрутить и логгер сделать
|
||||
using System;
|
||||
using System;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Зависимости:<br/>
|
||||
/// - UnityMainThreadDispatcher<br/>
|
||||
/// - ComNativeWrapper<br/>
|
||||
/// </summary>
|
||||
public static class ComWrapper
|
||||
{
|
||||
//Настраиваем из инспектора
|
||||
//То, что нужно вынести в конфиг
|
||||
//[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||||
//[SerializeField] bool keepAlive = true;
|
||||
static bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true. Да и что то не вижу реализации. Возможно оно уже где-то в ComInput
|
||||
static bool keepAlive = true;
|
||||
|
||||
public static UnityEvent PaymentReceived;
|
||||
public static event Action 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]; //Байт-буфер куда будут приходить данные с порта
|
||||
private const string REQUEST_ENCRYPTED_COMMAND = "666";
|
||||
private const string REQUEST_CONFIRMATION_COMMAND = "777";
|
||||
|
||||
private static CancellationTokenSource _lifetimeCts;
|
||||
private static Thread _readerThread;
|
||||
private static CancellationTokenSource _readerCts;
|
||||
|
||||
private static int h;
|
||||
private static ILogger logger;
|
||||
|
||||
private static CancellationTokenSource poll_cts, keepAlive_cts;
|
||||
private static int _lastInputTick = 0;
|
||||
private const int INPUT_IDLE_BEFORE_PING_MS = 60_000;//1 минута
|
||||
|
||||
|
||||
public static void Initialize()//Добавить логгер в конструктор
|
||||
{
|
||||
|
||||
//Тут надо с конфига подтянуть настройки!
|
||||
|
||||
PaymentReceived = new UnityEvent();
|
||||
if (_lifetimeCts != null) return;
|
||||
OpenPort();
|
||||
SetupBoardAsync();
|
||||
if (h <= 0)
|
||||
{
|
||||
Log("Порт не открылся при инициализации");
|
||||
return;
|
||||
}
|
||||
|
||||
UnityMainThreadDispatcher.Initialize();
|
||||
_lifetimeCts = new CancellationTokenSource();
|
||||
|
||||
|
||||
StartReaderThread();
|
||||
_ = SetupBoardAsync(_lifetimeCts.Token);
|
||||
}
|
||||
|
||||
public static void Send(string message)
|
||||
{
|
||||
if (h <= 0) return;
|
||||
try
|
||||
{
|
||||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
|
||||
}
|
||||
catch { logger?.Log("Ошибка отправки строкового сигнала."); }
|
||||
catch { logger?.Log($"Ошибка отправки строкового сигнала. {message}"); }
|
||||
}
|
||||
|
||||
private static async Task SetupBoardAsync()
|
||||
private static void StartReaderThread()
|
||||
{
|
||||
bool auth = await BoardAuthAsync();
|
||||
if(!auth) (new GameObject()).AddComponent(typeof(VfxPrefab));//Запускаем отвал
|
||||
StopReaderThread();
|
||||
|
||||
poll_cts = new CancellationTokenSource();
|
||||
keepAlive_cts = new CancellationTokenSource();
|
||||
_readerCts = new CancellationTokenSource();
|
||||
var ct = _readerCts.Token;
|
||||
|
||||
StartPollingAsync();
|
||||
StartKeepAliveProcessAsync(keepAlive_cts.Token);
|
||||
_readerThread = new Thread(() =>
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
if (h <= 0) { Thread.Sleep(20); continue; }
|
||||
int read = 0;
|
||||
try
|
||||
{
|
||||
//блок до таймаута чтения
|
||||
read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buffer, buffer.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log("Com reader exception: " + ex.Message);
|
||||
Thread.Sleep(50);// пауза чтобы не уйти в спин при ошибке
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
var line = Encoding.UTF8.GetString(buffer, 0, read).Trim();
|
||||
EnqueueLine(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_readerThread.IsBackground = true;
|
||||
_readerThread.Start();
|
||||
}
|
||||
|
||||
private static void StopReaderThread()
|
||||
{
|
||||
if(_readerCts != null)
|
||||
{
|
||||
_readerCts.Cancel();
|
||||
_readerCts.Dispose();
|
||||
_readerCts = null;
|
||||
}
|
||||
|
||||
if(_readerThread != null)
|
||||
{
|
||||
_readerThread.Join(200);
|
||||
_readerThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
static void EnqueueLine(string line)
|
||||
{
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() => OnLine(line));
|
||||
}
|
||||
|
||||
private static void ProcessComInput(string line)
|
||||
{
|
||||
Log($"COM. h={h}: {line}");
|
||||
|
||||
if (line == "COIN")
|
||||
PaymentReceived?.Invoke();
|
||||
else if (line.StartsWith("INPUT"))
|
||||
{
|
||||
Volatile.Write(ref _lastInputTick, Environment.TickCount);
|
||||
InputRecieved?.Invoke(line.Substring(5));
|
||||
}
|
||||
}
|
||||
|
||||
static readonly object _waitLock = new();
|
||||
static readonly List<Waiter> _waiters = new();
|
||||
sealed class Waiter
|
||||
{
|
||||
public Func<string, bool> Match;
|
||||
public TaskCompletionSource<string> Tcs;
|
||||
}
|
||||
static Task<string> WaitForAsync(Func<string, bool> match, int timeoutMs, CancellationToken ct)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var w = new Waiter { Match = match, Tcs = tcs };
|
||||
|
||||
lock (_waitLock) _waiters.Add(w);//
|
||||
|
||||
//отмена
|
||||
if (ct.CanBeCanceled)//
|
||||
{
|
||||
ct.Register(() =>
|
||||
{
|
||||
lock (_waitLock) _waiters.Remove(w);
|
||||
tcs.TrySetCanceled(ct);
|
||||
});
|
||||
}
|
||||
|
||||
_ = Task.Delay(timeoutMs, CancellationToken.None).ContinueWith(_ =>
|
||||
{
|
||||
//if (tcs.Task.IsCompleted) return;
|
||||
lock (_waitLock) _waiters.Remove(w);
|
||||
tcs.TrySetException(new TimeoutException($"Timeout waiting for line"));
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
static Task<string> WaitForPrefixAsync(string prefix, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => s.StartsWith(prefix, StringComparison.Ordinal), timeoutMs, ct);
|
||||
static Task<string> WaitForContainsAsync(string prefix, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => s.IndexOf(prefix, StringComparison.Ordinal) >= 0, timeoutMs, ct);
|
||||
|
||||
static void OnLine(string line)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
return;
|
||||
|
||||
//1. обычная обработка
|
||||
ProcessComInput(line);
|
||||
|
||||
//2. Разубудить ожидающих
|
||||
List<Waiter> toFire = null;
|
||||
|
||||
lock (_waitLock)
|
||||
{
|
||||
for (int i = _waiters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var waiter = _waiters[i];
|
||||
if (waiter.Match(line))
|
||||
{
|
||||
_waiters.RemoveAt(i);
|
||||
(toFire ??= new List<Waiter>()).Add(waiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toFire != null)
|
||||
foreach (var w in toFire)
|
||||
w.Tcs.TrySetResult(line);
|
||||
}
|
||||
|
||||
private static async Task SetupBoardAsync(CancellationToken ct)
|
||||
{
|
||||
bool auth = await BoardAuthAsync(ct);
|
||||
if (!auth)
|
||||
{
|
||||
Log("Авторизация на плате не пройдена.");
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() => Application.Quit());
|
||||
return;
|
||||
}
|
||||
|
||||
_ = StartKeepAliveProcessAsync(ct);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static void OpenPort()
|
||||
{
|
||||
Log("Инициализация порта");
|
||||
//ComNativeWrapper.Serial_CloseAll();//или все таки h?, Пока не удаляем, посмотрим как сработает
|
||||
|
||||
|
||||
var ports = ComNativeWrapper.ListPorts();
|
||||
if (ports.Length == 0) { Log("Нет портов, завершаю"); return; }
|
||||
@ -88,148 +244,77 @@ public static class ComWrapper
|
||||
}
|
||||
}
|
||||
|
||||
//прописать нормальные логи
|
||||
private static async Task<bool> BoardAuthAsync()
|
||||
private static async Task<bool> BoardAuthAsync(CancellationToken ct)
|
||||
{
|
||||
bool boardEncrypted = true; //предполагаем что зашифрована
|
||||
float timeout = 1;
|
||||
var sw = new Stopwatch();
|
||||
|
||||
//Проверяем зашифрована или нет
|
||||
sw.Start();
|
||||
RequestInputs();
|
||||
while (sw.Elapsed.TotalSeconds < timeout)
|
||||
try
|
||||
{
|
||||
string entry = await ReadBufferLineAsync();
|
||||
if (entry.StartsWith("INPUT"))
|
||||
{
|
||||
boardEncrypted = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
sw.Stop();
|
||||
|
||||
//Не шифрованная -> На выход, пользуемся.
|
||||
if (!boardEncrypted)
|
||||
Send(REQUEST_INPUT_COMMAND);
|
||||
var inputLine = await WaitForPrefixAsync("INPUT", 1000, ct);
|
||||
return true;
|
||||
|
||||
|
||||
//А тут возможно шифрованная
|
||||
|
||||
Log("Шифрованная плата");
|
||||
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
//Не пришел инпут
|
||||
Log("Шифрованная плата");
|
||||
}
|
||||
|
||||
//Запрашиваем шифрованную строку
|
||||
string encryptedString = string.Empty;
|
||||
Send("666");
|
||||
sw.Start();
|
||||
while (sw.Elapsed.Seconds < timeout)
|
||||
string encLine;
|
||||
try
|
||||
{
|
||||
string entry = await ReadBufferLineAsync();
|
||||
if (entry.StartsWith("ENC:"))
|
||||
{
|
||||
//пишем сразу без суффикса
|
||||
encryptedString = entry.Substring(4);
|
||||
break;
|
||||
}
|
||||
Send(REQUEST_ENCRYPTED_COMMAND);
|
||||
encLine = await WaitForPrefixAsync("ENC:", 1000, ct);
|
||||
}
|
||||
sw.Stop();
|
||||
|
||||
//Шифрованная строка не пришла -> идем нафиг
|
||||
if (string.IsNullOrEmpty(encryptedString))
|
||||
catch
|
||||
{
|
||||
Log($"Зашифрованная строка не пришла");
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
//Раз пришла, надо расшифровывать
|
||||
Log($"Расшифровываю {encryptedString}");
|
||||
string decryptedString = BoardUtility.DecryptSerial(encryptedString);
|
||||
//Расшифровка
|
||||
var encrypted = encLine.Substring(4);
|
||||
var decrypted = BoardUtility.DecryptSerial(encrypted);
|
||||
|
||||
//ждем подтверждение от платы
|
||||
string confirmationString = string.Empty;
|
||||
sw.Start();
|
||||
Send("777" + decryptedString);
|
||||
while (sw.Elapsed.Seconds < timeout)
|
||||
//Отправляем расшифрованное на подтверждение
|
||||
try
|
||||
{
|
||||
string entry = await ReadBufferLineAsync();
|
||||
if (entry.IndexOf("OK") != -1)
|
||||
{
|
||||
//Успех, плату активировали
|
||||
sw.Stop();
|
||||
return true;
|
||||
}
|
||||
Send(REQUEST_CONFIRMATION_COMMAND + decrypted);
|
||||
await WaitForContainsAsync("OK", 1000, ct);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log("Подтверждение не пришло");
|
||||
return false;
|
||||
}
|
||||
|
||||
//А вот тут мы не дождались подтверждения. Плохо, будем ломать игру.
|
||||
Application.Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void OnApplicationQuit()
|
||||
public static void Shutdown()
|
||||
{
|
||||
StopAsyncPolling();
|
||||
StopKeepAlive();
|
||||
_lifetimeCts?.Cancel();
|
||||
_lifetimeCts?.Dispose();
|
||||
_lifetimeCts = null;
|
||||
|
||||
StopReaderThread();
|
||||
CancelAllWaiters();
|
||||
ClosePort();
|
||||
}
|
||||
|
||||
private static void StopAsyncPolling()
|
||||
private static void CancelAllWaiters()
|
||||
{
|
||||
poll_cts?.Cancel();
|
||||
poll_cts?.Dispose();
|
||||
poll_cts = null;
|
||||
}
|
||||
|
||||
private static void StopKeepAlive()
|
||||
{
|
||||
keepAlive_cts?.Cancel();
|
||||
keepAlive_cts?.Dispose();
|
||||
keepAlive_cts = null;
|
||||
}
|
||||
|
||||
//прописать норм логи
|
||||
private static async void StartPollingAsync()
|
||||
{
|
||||
try
|
||||
List<Waiter> snapshot;
|
||||
lock (_waitLock)
|
||||
{
|
||||
Log("Запуск асинхронного опроса порта");
|
||||
|
||||
while (poll_cts != null && !poll_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(10, poll_cts.Token);
|
||||
|
||||
string comInput = await ReadBufferLineAsync(poll_cts.Token);
|
||||
if (!string.IsNullOrEmpty(comInput))
|
||||
{
|
||||
// Прямой вызов - мы уже в главном потоке после await
|
||||
ProcessComInputImmediate(comInput);
|
||||
}
|
||||
}
|
||||
snapshot = new List<Waiter>(_waiters);
|
||||
_waiters.Clear();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
foreach (Waiter waiter in snapshot)
|
||||
{
|
||||
Log("Асинхронный опрос порта остановлен");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"Ошибка асинхронного опроса: {e.Message}");
|
||||
waiter.Tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
//Логи
|
||||
private static void ProcessComInputImmediate(string comInput)
|
||||
{
|
||||
Log($"COM. h={h}: {comInput}");
|
||||
|
||||
if (comInput == "COIN")
|
||||
PaymentReceived?.Invoke();
|
||||
else if (comInput.StartsWith("INPUT"))
|
||||
InputRecieved?.Invoke(comInput.Substring(5));
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
private static void ClosePort()
|
||||
{
|
||||
Log($"Отпускаю порт handle:{h}");
|
||||
@ -239,56 +324,30 @@ public static class ComWrapper
|
||||
h = 0;
|
||||
}
|
||||
}
|
||||
private static async Task<string> ReadBufferLineAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
// Проверяем отмену внутри Task.Run
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return string.Empty;
|
||||
|
||||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
|
||||
if (read > 0)
|
||||
{
|
||||
return Encoding.UTF8.GetString(buf, 0, read);
|
||||
}
|
||||
return string.Empty;
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log("Чтение порта отменено");
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"Ошибка асинхронного чтения: {e.Message}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RequestInputs()
|
||||
=> Send(REQUEST_INPUT_COMMAND);
|
||||
|
||||
|
||||
//Сделать ожидание простоя около минуты на проверку и переподелючение.
|
||||
private static async Task StartKeepAliveProcessAsync(CancellationToken cancellationToken)
|
||||
private static async Task StartKeepAliveProcessAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log("Запуск асинхронной проверки порта");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
if (!await CheckPortConnectionAsync())
|
||||
if (!IsInputIdleTooLong())
|
||||
{
|
||||
Log("Плата отвалилась. Запускаю переподключение.");
|
||||
await ReinitializePortAsync(cancellationToken);
|
||||
await Task.Delay(5000, ct);
|
||||
continue;
|
||||
}
|
||||
|
||||
await Task.Delay(10000, cancellationToken); //Задержка
|
||||
//Пингуем плату
|
||||
if (!await CheckPortConnectionAsync(ct))
|
||||
{
|
||||
Log("Плата отвалилась. Запускаю переподключение.");
|
||||
await ReinitializePortAsync(ct);
|
||||
}
|
||||
|
||||
await Task.Delay(10000, ct);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@ -300,32 +359,53 @@ public static class ComWrapper
|
||||
Log($"Ошибка проверки порта: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static async Task<bool> CheckPortConnectionAsync()
|
||||
|
||||
private static async Task<bool> CheckPortConnectionAsync(CancellationToken ct)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
float timeout = 1;
|
||||
RequestInputs();
|
||||
while (sw.Elapsed.Seconds < timeout)
|
||||
try
|
||||
{
|
||||
string entry = await ReadBufferLineAsync();
|
||||
if(!string.IsNullOrEmpty(entry) && entry.StartsWith("INPUT"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Send(REQUEST_INPUT_COMMAND);
|
||||
await WaitForPrefixAsync("INPUT", 1000, ct);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
sw.Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
//реализовать
|
||||
private static async Task ReinitializePortAsync(CancellationToken cancellationToken)
|
||||
private static async Task ReinitializePortAsync(CancellationToken ct)
|
||||
{
|
||||
StopReaderThread();
|
||||
CancelAllWaiters();
|
||||
ClosePort();
|
||||
await SetupBoardAsync();
|
||||
}
|
||||
|
||||
OpenPort();
|
||||
if (h <= 0)
|
||||
{
|
||||
Log($"Ошибка при переподключении.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
StartReaderThread();
|
||||
var auth = await BoardAuthAsync(ct);
|
||||
if (!auth)
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() => (new GameObject()).AddComponent(typeof(VfxPrefab)));
|
||||
}
|
||||
|
||||
static bool IsInputIdleTooLong()
|
||||
{
|
||||
int last = Volatile.Read(ref _lastInputTick);
|
||||
if (last == 0) return true;
|
||||
int now = Environment.TickCount;
|
||||
|
||||
// переполнение TickCount тут ок: сравнение интервала < 24 дней работает корректно
|
||||
int elapsed = unchecked(now - last);
|
||||
return elapsed >= INPUT_IDLE_BEFORE_PING_MS;
|
||||
}
|
||||
|
||||
private static void Log(string message) => logger?.Log($"CW{message}");
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ public class UnityMainThreadDispatcher : MonoBehaviour
|
||||
if (instance == null)
|
||||
{
|
||||
var go = new GameObject("UnityMainThreadDispatcher");
|
||||
//DontDestroyOnLoad(go);
|
||||
DontDestroyOnLoad(go);
|
||||
instance = go.AddComponent<UnityMainThreadDispatcher>();
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ public class UnityMainThreadDispatcher : MonoBehaviour
|
||||
if (instance == null)
|
||||
{
|
||||
var go = new GameObject("UnityMainThreadDispatcher");
|
||||
//DontDestroyOnLoad(go);
|
||||
DontDestroyOnLoad(go);
|
||||
instance = go.AddComponent<UnityMainThreadDispatcher>();
|
||||
}
|
||||
return instance;
|
||||
|
||||
@ -24,7 +24,7 @@ public static class SingleWallet
|
||||
onCoinAdded = new UnityEvent<int>();
|
||||
onCoinRemoved = new UnityEvent<int>();
|
||||
|
||||
ComWrapper.PaymentReceived.AddListener(AddCredit);
|
||||
ComWrapper.PaymentReceived += AddCredit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user