В общем пока остановился на такой реализации, чего то более "умного" не придумалось пока
Используется csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Microsoft.AspNetCore.SignalR.Client;
public class SignalRCoreClient : MonoBehaviour
{
object locker = new object();
List<Action> functionsToRunInMainThread;
static HubConnection hubConnection;
static DateTime startTime;
public Text debug;
bool isRunning;
bool isChatMessage;
private void Start()
{
Screen.sleepTimeout = SleepTimeout.NeverSleep;
functionsToRunInMainThread = new List<Action>();
try
{
var rnd = UnityEngine.Random.Range(1, 100);
hubConnection = new HubConnectionBuilder()
.WithUrl("http://127.0.0.1:8088/MyHub", options => options.Headers.Add("UserName", $"[{rnd}] Saltant"))
.Build();
hubConnection.StartAsync();
}
catch (Exception ex)
{
debug.text = ex.Message;
}
hubConnection.On<string, string>("AddMessage", (user, message) =>
{
Action aFunction = () => {
StartCoroutine(enumerator());
IEnumerator enumerator()
{
while (isChatMessage)
yield return null;
isChatMessage = true;
debug.text = $"({DateTime.Now.ToLongTimeString()}) {user}: {message} (Latency: {Math.Round(DateTime.Now.Subtract(startTime).TotalMilliseconds)} ms.)";
isChatMessage = false;
}
};
if (!isChatMessage)
QueueMainThreadFunction(aFunction);
});
InvokeRepeating("ToSend", 0, 1);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
Application.Quit();
if (!isRunning)
StartCoroutine(CheckQueueCo());
}
IEnumerator CheckQueueCo()
{
while (functionsToRunInMainThread.Count > 0)
{
isRunning = true;
Action someFunc = functionsToRunInMainThread[0];
functionsToRunInMainThread.RemoveAt(0);
someFunc();
}
if(isRunning)
isRunning = false;
yield return null;
}
public void QueueMainThreadFunction(Action someFunction)
{
lock (locker)
functionsToRunInMainThread.Add(someFunction);
}
public void ToSend()
{
Task.Run(() => SendMessageToServer("Test Message"));
}
public async Task SendMessageToServer(string message)
{
startTime = DateTime.Now;
await hubConnection.InvokeAsync("Send", message);
}
private void OnApplicationQuit()
{
if(hubConnection.State.Equals(HubConnectionState.Connected))
hubConnection.StopAsync();
}
}
Схема работы такова:В методе Start() происходит коннект к хабу SignalR, вызов функции ToSend() каждую секунду, подписка на событие "AddMessage" которое возвращает 2 стринг аргумента, первый имя юзера отправляющего запрос, второе собсна его сообщение. В этом событии я ловлю переменные эти и создаю Action, в моем случае, он содержит в себе метод StartCoroutine() который запускает IEnumerator в том же Action, почему корутины? О этом чуть дальше будет понятно.
Далее после создания Action я передаю его в метод QueueMainThreadFunction(Action), этот метод лочит с помощью пустого object - List<Action>, лочить нужно чтоб одновременно несколько потоков не смогли запихивать экшены в лист, только последовательно (возможно это излишняя перестраховка, но пусть будет), этот лист содержит в себе все экшены из скрипта которые нужно выполнить в мейн треде (в главном потоке, который уже может получать доступ к объектам MonoBehaviour).
В методе Update() я каждый кадр проверяю появились ли новые Action в экшен листе и если они там есть то запускаю их на выполнение уже от главного потока (метод Update() всегда дёргается только из главного потока), вначале идет проверка не запущена ли уже корутина с запуском экшенов, если не запущена - запускаю, если запущена - смотрю в следующем кадре.
В самой корутине срабатывает while пока в листе не кончатся экшены, цикл таков - в локальной переменной создаю первый экшен из листа, удаляю этот экшен из листа и запускаю локальный экшен. Запущенные корутины в экшенах не блокируют основной поток, из листа с экшенами я могу одновременно запустить целую гору методов и все будет хорошо с основным потоком, по этому были выбраны именно корутины и IEnumerator в экшенах.
По сути корутина с CheckQueueCo() может вообще никогда не прекратиться т.к while будет работать до тех пор пока есть объекты в листе, а они туда могут сыпаться из RPC калбеков горами, успевай запускать и удалять как говорится, но т.к while работает в корутине - основной поток от этого не страдает.
Вот такая странная тема чтоб работать с объектами юнити получая данные по удаленке (RPC), не знаю на сколько продуктивна такая схема, задержка по времени с 4G интернета на мобильном девайсе выходит ~80мс от старта запроса на сервер с передачей стринга и до момента вставки ответного стринга в объект MonoBehaviour, с вафли (на локальный сервак, не на локалхост, а на другую машину в сети) задержка ~50мс, так что полагаю быстрее юнити просто не в состоянии работать с RPC калбеками.
Написал эту стену текста для тех кто тоже задается этим вопросом по работе с unity RPC, возможно кому то поможет что то понять, или люди поделятся своими более "умными" решениями.