Жопа подгорает и/или ущербный Unity3D

Форум для самых маленьких, а так же тех, кому недосуг читать справку самостоятельно.

Жопа подгорает и/или ущербный Unity3D

Сообщение Saltant 28 окт 2019, 21:48

Потушите кто нибудь! Вот почему, почему нельзя получив RPC вызов с данными с сервера моментально что то сделать в клиенте с игровым объектом оперируя этими данными? почему юнитеки запретили доступ к объектам не из мейн треда? не ну я понимаю что можно нафакапить, особенно если не лочить, но почему нет нигде галочки в настройках типа "я принимаю всю ответственность за последствия, снять ограничения с юнити"? Ну ведь такой бред получается, - сначала получить данные с сервера, сохранить куда то эти данные, дёрнуть эти данные мейн тредом и только им уже присвоить их куда нужно ~x( На столько простой код можно было писать и главное короткий, что то типа:
Синтаксис:
Используется csharp
hubConnection.On<string, string>("Receive", (user, message) =>
{
     GameObject.GetComponent<Text>().text = $"{user}: {message}";
});

Это на примере моего SignalR сервера, тупо одной стройчкой подписываюсь и слушаю ивент "Receive" от сервера, как получаю стринги user и message, сразу бы брал нужный объект и пихал туда, но нет, нифига! доступ не из мейн треда запрещен!
Какие есть лучшие способы передавать данные полученные с помощью RPC? Если брать в учет что данных сыпаться в секунду может тонны по разным вызовам, да и по одному и тому же вызову по 100 в секунду легко высылаться тоже.
Я на Google Play _https://play.google.com/store/apps/developer?id=Saltant
Аватара пользователя
Saltant
Адепт
 
Сообщения: 2234
Зарегистрирован: 09 окт 2018, 16:40
Откуда: Химки
  • Сайт

Re: Жопа подгорает и/или ущербный Unity3D

Сообщение Saltant 29 окт 2019, 14:21

В общем пока остановился на такой реализации, чего то более "умного" не придумалось пока

Синтаксис:
Используется 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, возможно кому то поможет что то понять, или люди поделятся своими более "умными" решениями.
Я на Google Play _https://play.google.com/store/apps/developer?id=Saltant
Аватара пользователя
Saltant
Адепт
 
Сообщения: 2234
Зарегистрирован: 09 окт 2018, 16:40
Откуда: Химки
  • Сайт


Вернуться в Почемучка

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 19