UnityTech / UIWidgets

UIWidget is a Unity Package which helps developers to create, debug and deploy efficient, cross-platform Apps.
Other
1.97k stars 256 forks source link

get_timeSinceStartup can only be called from the main thread #445

Closed lincoln310 closed 3 years ago

lincoln310 commented 3 years ago

hi, 我用uiwidgets制作了一个界面,用来对接mqtt服务和数据的。 我用了M2MqttUnity这个包,然后重新封装了一个类,把unity的MonoBehavior去掉了(主要是不知道怎么结合uiwidgets和这种MonoBehavior)。

目前的效果是:能正常的连接到服务器,能订阅topic,也能收到数据。但是不能实时的更新界面。

我对unity和界面都是新手,看了ConnectAppCN的代码,也没找到合适的内容。能否提供一些帮助?谢谢!

code: image image

/*
The MIT License (MIT)

Copyright (c) 2018 Giovanni Paolo Vigano'

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;

/// <summary>
/// Adaptation for Unity of the M2MQTT library (https://github.com/eclipse/paho.mqtt.m2mqtt),
/// modified to run on UWP (also tested on Microsoft HoloLens).
/// </summary>
namespace M2MqttUnity
{
    /// <summary>
    /// Generic MonoBehavior wrapping a MQTT client, using a double buffer to postpone message processing in the main thread. 
    /// </summary>
    public class PureM2MqttUnityClient //: MonoBehaviour
    {
        [Header("MQTT broker configuration")]
        [Tooltip("IP address or URL of the host running the broker")]
        public string brokerAddress = "localhost";
        [Tooltip("Port where the broker accepts connections")]
        public int brokerPort = 1883;
        [Tooltip("Use encrypted connection")]
        public bool isEncrypted = false;
        [Header("Connection parameters")]
        [Tooltip("Connection to the broker is delayed by the the given milliseconds")]
        public int connectionDelay = 500;
        [Tooltip("Connection timeout in milliseconds")]
        public int timeoutOnConnection = MqttSettings.MQTT_CONNECT_TIMEOUT;
        [Tooltip("Connect on startup")]
        public bool autoConnect = true;
        [Tooltip("UserName for the MQTT broker. Keep blank if no user name is required.")]
        public string mqttUserName = null;
        [Tooltip("Password for the MQTT broker. Keep blank if no password is required.")]
        public string mqttPassword = null;

        /// <summary>
        /// Wrapped MQTT client
        /// </summary>
        protected MqttClient client;

        /// <summary>
        /// Event fired when a connection is successfully established
        /// </summary>
        public event Action ConnectionSucceeded;
        /// <summary>
        /// Event fired when failing to connect
        /// </summary>
        public event Action ConnectionFailed;

        /// <summary>
        /// Connect to the broker using current settings.
        /// </summary>
        public virtual void Connect()
        {
            try
            {
                if (client == null)
                {
                    client = new MqttClient(brokerAddress, brokerPort, isEncrypted, null, null, isEncrypted ? MqttSslProtocols.SSLv3 : MqttSslProtocols.None);
                }
                client.Settings.TimeoutOnConnection = timeoutOnConnection;
                string clientId = Guid.NewGuid().ToString();
                client.Connect(clientId, mqttUserName, mqttPassword);
                if (client.IsConnected)
                {
                    client.ConnectionClosed += OnMqttConnectionClosed;
                    // register to message received 
                    client.MqttMsgPublishReceived += OnMqttMessageReceived; 
                    OnConnected();
                }
                else
                {
                    OnConnectionFailed("CONNECTION FAILED!");
                }
            }
            catch (Exception e)
            {
                client = null;
                Debug.LogErrorFormat("Failed to connect to {0}:{1}:\n{2}", brokerAddress, brokerPort, e.ToString());
                OnConnectionFailed(e.Message);
            }
        }

        /// <summary>
        /// Disconnect from the broker, if connected.
        /// </summary>
        public virtual void Disconnect()
        {
            if (client != null)
            {
                CloseConnection();
                OnDisconnected();
            }
        }

        /// <summary>
        /// Override this method to take some actions before connection (e.g. display a message)
        /// </summary>
        protected virtual void OnConnecting()
        {
            Debug.LogFormat("Connecting to broker on {0}:{1}...\n", brokerAddress, brokerPort.ToString());
        }

        /// <summary>
        /// Override this method to take some actions if the connection succeeded.
        /// </summary>
        protected virtual void OnConnected()
        {
            Debug.LogFormat("Connected to {0}:{1}...\n", brokerAddress, brokerPort.ToString());

            SubscribeTopics();

            if (ConnectionSucceeded != null)
            {
                ConnectionSucceeded();
            }
        }

        /// <summary>
        /// Override this method to take some actions if the connection failed.
        /// </summary>
        protected virtual void OnConnectionFailed(string errorMessage)
        {
            Debug.LogWarning("Connection failed.");
            if (ConnectionFailed != null)
            {
                ConnectionFailed();
            }
        }

        /// <summary>
        /// Override this method to subscribe to MQTT topics.
        /// </summary>
        protected virtual void SubscribeTopics()
        {
        }

        /// <summary>
        /// Override this method to unsubscribe to MQTT topics (they should be the same you subscribed to with SubscribeTopics() ).
        /// </summary>
        protected virtual void UnsubscribeTopics()
        {
        }

        /// <summary>
        /// Disconnect before the application quits.
        /// </summary>
        protected virtual void OnApplicationQuit()
        {
            CloseConnection();
        }

        /// <summary>
        /// Override this method to take some actions when disconnected.
        /// </summary>
        protected virtual void OnDisconnected()
        {
            Debug.Log("Disconnected.");
        }

        protected virtual void OnMqttConnectionClosed(object sender, EventArgs e)
        {
            // Set unexpected connection closed only if connected (avoid event handling in case of controlled disconnection)
            Fx.SleepThread(connectionDelay);
            Connect();
        }

        /// <summary>
        /// Override this method for each received message you need to process.
        /// </summary>
        protected virtual void DecodeMessage(string topic, byte[] message)
        {
            Debug.LogFormat("Message received on topic: {0}", topic);
        }

        private void OnMqttMessageReceived(object sender, MqttMsgPublishEventArgs msg)
        {
            DecodeMessage(msg.Topic, msg.Message);
        }

        private void CloseConnection()
        {
            if (client != null)
            {
                client.ConnectionClosed -= OnMqttConnectionClosed;
                if (client.IsConnected)
                {
                    UnsubscribeTopics();
                    client.Disconnect();
                }
                client.MqttMsgPublishReceived -= OnMqttMessageReceived;
                client = null;
            }
        }
    }
}

stack:

[Native Transition]
Timer.get_timeSinceStartup() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/async/timer.cs:line 21
new TimerProvider.TimerImpl() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/async/timer.cs:line 149
TimerProvider.run() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/async/timer.cs:line 83
WindowAdapter.run() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/editor/editor_window.cs:line 560
WindowAdapter.scheduleFrame() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/editor/editor_window.cs:line 527
UIWidgetWindowAdapter.scheduleFrame() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/engine/UIWidgetsPanel.cs:line 32
SchedulerBinding.scheduleFrame() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/scheduler/binding.cs:line 183
SchedulerBinding.ensureVisualUpdate() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/scheduler/binding.cs:line 161
WidgetsBinding._handleBuildScheduled() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/widgets/binding.cs:line 137
BuildOwner.scheduleBuildFor() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/widgets/framework.cs:line 889
Element.markNeedsBuild() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/widgets/framework.cs:line 2048
State.setState() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/widgets/framework.cs:line 508
_StoreListenerState<GlobalState, MqttModel>._innerStateChanged() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/redux/widget_redux.cs:line 164
_StoreListenerState<GlobalState, MqttModel>._handleStateChanged() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/redux/widget_redux.cs:line 141
Store<GlobalState>._innerDispatch() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/redux/store.cs:line 75
DispatcherImpl.dispatch<TopicModel>() at /Users/linpeng/GitHub/M2MqttUnity/Packages/UIWidgets/Runtime/redux/store.cs:line 23
BrokerModel.addMsg() at /Users/linpeng/GitHub/M2MqttUnity/Assets/Unitter/MqttMsgData.cs:line 268
BrokerModel.MqttClient.DecodeMessage() at /Users/linpeng/GitHub/M2MqttUnity/Assets/Unitter/MqttMsgData.cs:line 193
PureM2MqttUnityClient.OnMqttMessageReceived() at /Users/linpeng/GitHub/M2MqttUnity/Assets/Unitter/PureM2MqttUnityClient.cs:line 206
MqttClient.OnMqttMsgPublishReceived() at /Users/linpeng/GitHub/M2MqttUnity/Assets/M2Mqtt/MqttClient.cs:line 889
MqttClient.DispatchEventThread() at /Users/linpeng/GitHub/M2MqttUnity/Assets/M2Mqtt/MqttClient.cs:line 1776
ThreadHelper.ThreadStart_Context()
ExecutionContext.RunInternal()
ExecutionContext.Run()
ExecutionContext.Run()
ThreadHelper.ThreadStart()
zhuxingwei commented 3 years ago

Thanks for your question!

I believe you may find something useful in https://github.com/UnityTech/ConnectAppCN/blob/master/Assets/ConnectApp/Utils/WebSocket/WebSocketHost.cs and its relevant classes, which will show you how to integrate a web socket plugin into a uiwidgets App.

ssssssilver commented 3 years ago

这个是UIWidgets的state类 在UIPanel类上实例化

`

using RSG; using System; using Unity.UIWidgets.painting; using Unity.UIWidgets.widgets; public class TestState : StatefulWidget { public override State createState() { return new TestStateState(); } } class TestStateState : State { bool isCall = false; int num=0; public override void initState() { TestMessage.instance.callBack += Add; Refresh(); } void Refresh() { Promise.Delayed(TimeSpan.FromSeconds(0.1f)).Then( () =>{ if (isCall) {setState(() => {isCall = false;num++;});} Refresh();}); } void Add() { isCall=true; } public override Widget build(BuildContext context) { return new Container( alignment: Alignment.center, child:new Text(num.ToString())); } }

这个类挂载到物体上 负责执行回调 using System; using UnityEngine;

public class TestMessage : MonoBehaviour { public static TestMessage instance; public Action callBack; // Start is called before the first frame update void Start() { instance = this; }

// Update is called once per frame
void Update()
{
    if (Input.GetKeyDown(KeyCode.A))
    {
        callBack?.Invoke();
    }
}

}

` 每次按A键 State类就会同步刷新 不知道能不能帮到你

lincoln310 commented 3 years ago

多谢,我用@ssssssilver的方式处理的。在state中记录了两个时间,一个msg更新时间,一个update处理时间,然后再update中检查两个时间,来触发action,更新界面。