Hi everyone, I rarely post in forums but this is something I can’t wrap my head around.
I’m trying to send a string path from a WPF application to Unity using MQTT. I’m using this library GitHub - vovacooper/Unity3d_MQTT: MQTT protocol running on Unity3d . So far I can get my path to Unity, that’s fine. However, when I try to call a method to open the path I receive, I get this error:
Here’s my code
using UnityEngine;
using System;
using System.Net;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.Text;
public class SimpleMQTT : MonoBehaviour
{
// Host IP adress
public string host;
// Port number - 1883 by default for easy test
public int port;
//Is it secured or not
public bool secured;
//The topics you want to receive
public string[] topicToSubscribe;
//Reference to MQTT client
private MqttClient client;
//Debug variables
private string publishTopic = "/Test";
private string publishMessage = "This is a test from Unity";
AudioFetch audioFetch;
// Use this for initialization
void Start()
{
audioFetch = GameObject.FindObjectOfType<AudioFetch>();
//Connect
Connect();
}
void Connect()
{
// create client instance
client = new MqttClient(IPAddress.Parse(host), port, secured, null);
//Register
Register();
}
void Register()
{
// register to message received
client.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
string clientId = Guid.NewGuid().ToString();
client.Connect(clientId);
//Subscribe
Subscribe();
}
private void Subscribe()
{
//Susbscribre to the topic specified // None by default
for (int i = 0; i < topicToSubscribe.Length; i++)
{
client.Subscribe(new string[] { topicToSubscribe[i] }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
}
}
void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
Debug.Log(Encoding.UTF8.GetString(e.Message));
audioFetch.GetAudio(Encoding.UTF8.GetString(e.Message));
}
//for debug purpose // will send message
void OnGUI()
{
if (GUI.Button(new Rect(20, 40, 80, 20), "Level 1"))
{
Debug.Log("Sending: " + publishMessage);
Publish();
}
}
void Publish()
{
client.Publish(publishTopic, Encoding.UTF8.GetBytes(publishMessage), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioFetch : MonoBehaviour {
AudioSource audioSource;
WWW www;
// Use this for initialization
void Start () {
audioSource = gameObject.GetComponent<AudioSource>();
}
public void GetAudio(string path)
{
www = new WWW(path);
audioSource.clip = www.GetAudioClip();
audioSource.Play();
}
}
The message says what happens.
Some Unity API call has been made on a different thread, probably due to the callback you’ve registered for the ‘MqttMsgPublishReceived’ event.
The library you’re using is not designed with Unity in mind, so you have to either avoid Unity API calls or dispatch / delay those in order to run that logic on the main thread.
So, in your case the following happens:
Data is received asynchronously and the ‘MqttMsgPublishReceived’ event will be raised on a seperate thread. Thus your subscribed method will be executed on this thread as well. That method attempts to access the audioFetch component by calling GetAudio, which is still okay… Technically you can access components and their members as long as no call to the Unity API is involved. There are some exceptions to this, such as Debug.Log, a few Utilities, some value types and a few other types.
However, WWW is apparently none of those, so that’s where it starts to fail when you create the WWW instance. This is part of the Unity API which is designed to only allow calls from the main thread.
Yes, I mean I kind of understand the error really. I guess my question was more, how can I run my other method on the main thread, instead of on the same thread I receive the message.
You can write a dispatcher that’ll be triggered on the main thread, e.g. every n-th frame or every n seconds.
The dispatcher can either be event-based or interface based. You can then add to it from another thread and trigger the execution from the main thread and reset the list. All the operations should be implemented with thread-safety.
Thank you for that pointer. I ended up using the dispatcher https://github.com/PimDeWitte/UnityMainThreadDispatcher and it works fine. In the mean time I also did a TCP communication system. It uses threads as well but it is possible to run a method on the main thread by using a bool in the update and switching it’s value when I receive data. I will still use the dispatcher as it is cleaner and MQTT gives me options to connect different kinds of devices.