I am having a little trouble when i get some data from websockets and try to display it through coroutines.
First, I have a classA attached to an object that opens the websocket and displays the data I receive:
public class ClassA : MonoBehaviour {
...
public IEnumerator ConnectWebSocket(url)
{
// in the websocket class, start the websocket connection, which
// will return data through a callback inside the return string
WebSocketClass.WebSocketStart(url, delegate(string result)
{
// receive websocket data and call the functions that displays it
WebSocketData(result);
});
// wait for the socket connection
while (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.CONNECTING)
{
yield return 0;
}
if (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.OPEN)
{
break;
}
...
}
// function that gets websocket data and starts couroutine to display it
public void WebSocketData(string data)
{
StartCoroutine(DisplayMessage(data));
}
}
But Unity complains with the next error:
StartCoroutine_Auto can only be called
from the main thread. Constructors and
field initializers will be executed
from the loading thread when loading a
scene. Don't use this function in the
constructor or field initializers,
instead move initialization code to
the Awake or Start function.
I searched in the unity forum and found this solution:
public class ClassA : MonoBehaviour {
...
public IEnumerator ConnectWebSocket(url)
{
// in the websocket class, start the websocket connection, which
// will return data through a callback inside the return string
WebSocketClass.WebSocketStart(url, delegate(string result)
{
// receive websocket data and call the functions that displays it
WebSocketData(result);
});
// wait for the socket connection
while (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.CONNECTING)
{
yield return 0;
}
if (WebSocketClass.WebSocketGetState() == WebSocketSharp.WebSocketState.OPEN)
{
break;
}
...
}
// function that gets websocket data and starts couroutine to display it
public void WebSocketData(string data)
{
DoOnMainThread.ExecuteOnMainThread.Enqueue(() => { StartCoroutine(DisplayMessage(data)); });
}
}
// class to manage the websocket data display inside the main thread
public class DoOnMainThread : MonoBehaviour
{
public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
public virtual void Update()
{
// dispatch stuff on main thread
while (ExecuteOnMainThread.Count > 0)
{
ExecuteOnMainThread.Dequeue().Invoke();
}
}
}
And it works! the problem is that even though I wrote the two classes in the same cs file and attached to an object, when I change the scene, return to that scene, and receive any data from the websocket, the next error is displayed:
MissingReferenceException: The object
of type 'ClassA' has been destroyed
but you are still trying to access it.
Your script should either check if it
is null or you should not destroy the
object.
UnityEngine.MonoBehaviour.StartCoroutine
(IEnumerator routine) (at
C:/BuildAgent/work/d63dfc6385190b60/artifacts/EditorGenerated/UnityEngineMonoBehaviour.cs:62)
I tried not destroying the object when a new scene is loaded, as the documentation says:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
But the error still appears.
The weird thing is that although there is an error, the data received from the websocket is displayed without any problem.
Does someone know how to avoid this problem? Any way to trigger a coroutine inside the main thread without using a second class? Or other solution to avoid this error?
Thanks!
I found the problem:
public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
It is static, so it becomes a problem when a public class is instantiated and generates another ExecuteOnMainThread.
So just deleted "static" and made it destroy and generate itself every time ClassA is created by Unity.
Now it works like a charm : )
Related
I have a System.Threading.Timer that receives some data each second, and I want its callback to put the received data into an object, which should be returned to client code. I tried to implement that using C#'s reference types mechanism. Here's the current callback:
private void Receive(object? obj) {
var data = GetData();
obj = (object)data; // Does nothing
}
And here's the method that starts the timer (timer is a field):
public void Start(Image image) {
timer = new Timer(
Receive,
image,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1)
);
}
Expected behavior: each second the data (which is image data) is received and put to the image, the reference to which is specified in parameter image.
Actual behavior: when I defined a variable of type Image, passed it to the Start method and checked its value (using the Visual Studio debugger) after 5 seconds, it was the empty Bitmap I assigned to it at the very start.
I have also checked that the data (containing non-empty image) is sent properly.
Can anybody tell me what's the issue, or, alternatively, suggest a different way to change the "state" object in a timer callback?
obj = (object)data; only sets the data locally in the Receive method. You could fix it by creating a wrapper object
public class ImageData
{
public Image Image { get; set; }
}
Then create a static instance of it that can be accessed from where you need it
public static readonly ImageData Data = new ImageData();
Depending on where you must access the images it can also an instance member and be private; however, it must always be the same instance that you used to start the timer.
Then start like this
public void Start() {
timer = new Timer(
Receive,
Data,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1)
);
}
And receive
private void Receive(object? obj) {
var data = (ImageData)obj;
data.Image = GetData();
}
Now, you can access the new images through the static Data.Image property.
Alternatively, you could "consume" the image (i.e., display or whatever you are doing with it) directly in the Receive callback and ignore the obj parameter. However, I do not have enough context information (where do the different code pieces reside and in which thread are they executed, etc.).
Update
The Image will be null in ImageData first, until the Receiver callback is called for the first time.
If you want an image at the very start, you can do several things.
Start the timer with the first time argument as 0 (this is the time it waits until firing the first time).
Call the callback directly in Start: Receive(Data).
Assign an image with Data.Image = GetData();.
Initialize the image in ImageData with a dummy image (maybe some kind of wait image).
Update 2
Apparently you are creating a library. I assume the library will pull images regularly and make them available to a consumer.
I suggest exposing an event in the library any consumer can subscribe to to receive images.
In the library we declare event arguments as
public class UpdateImageEventArgs : EventArgs
{
public UpdateImageEventArgs(Image image)
{
Image = image;
}
public Image Image { get; }
}
A possible implementation of the library:
public class Library : IDisposable
{
public event EventHandler<UpdateImageEventArgs>? ImageUpdated;
private System.Threading.Timer? _timer;
public void Start()
{
_timer = new System.Threading.Timer(Receive, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
private void Receive(object? obj)
{
var image = GetImage();
ImageUpdated?.Invoke(this, new UpdateImageEventArgs(image));
}
private static Image GetImage()
{
// TODO: Add your implementation
throw new NotImplementedException();
}
public void Dispose()
{
if (_timer != null) {
_timer.Dispose();
_timer = null;
}
}
}
The consumer code:
public static void Test()
{
var library = new Library();
library.ImageUpdated += Library_ImageUpdated;
library.Start();
}
private static void Library_ImageUpdated(object? sender, UpdateImageEventArgs e)
{
DoSomethingWith(e.Image);
}
If you want to stop the timer, you can do so by calling library.Dispose();. This also ensures that the timer resources are disposed properly.
In the Recieve method you get an obj as input reference. Then you try to set a value to obj. This last step cannot work, since you work on the reference.
The only way it could work is, if the input reference is given as a ref, which is
private void Receive(ref object? obj) {}
Passing a reference type by reference enables the called method to replace the object to which the reference parameter refers in the caller.
I have a custom scene manager, which apart from loading scenes calls some events for me. I want it to fully load a scene, before it starts loading another one, so I added a lock. I'm using Monitor because I want to enter the lock in OnLoadScene() and exit it in LoadSceneAsync() and this wouldn't be possible with the lock() {} syntax, because LoadSceneAsync() is called as a Coroutine (asynchronously). I found the Monitor syntax here: C# manual lock/unlock.
Here is the code:
public class CustomSceneManager : Singleton<CustomSceneManager>
{
public delegate void SceneChange(string sceneName);
public event SceneChange LoadScene;
public event SceneChange UnloadScene;
private static readonly object LoadSceneLock = new object();
private IEnumerator LoadSceneAsync(string sceneName)
{
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone)
{
yield return null;
}
Debug.Log($"Finished loading: {sceneName}");
LoadScene?.Invoke(sceneName);
Monitor.Exit(LoadSceneLock); // exit lock
}
public void OnLoadScene(string newSceneName)
{
Monitor.Enter(LoadSceneLock); // enter lock
Debug.Log($"Started loading: {newSceneName}");
UnloadScene?.Invoke(newSceneName);
StartCoroutine(LoadSceneAsync(newSceneName));
}
}
The issue is that it doesn't work as I expect it to. This is how my logs look:
Started loading: scene_1
Started loading: scene_2
Finished loading: scene_1
Finished loading: scene_2
Am I using the lock incorrectly?
I am stuck with this problem for 3 days, I did a lot of research, but couldn't find any answer, Here is a brief explanation of what is happening, trying to work with Firebase Database and Authentication with Unity3D, Here are the steps:
First user signs in and if it's successful, it will fetch user's data from Database and then Authentication panel should be deactivated and User's panel activated.
It Gives me this error when trying to SetActive panel.
SetActive can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityEngine.GameObject:SetActive(Boolean)
public void SignInWithEmail()
{
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
//here after successful signing in, it gets the data from the Database
//and after that it should activate the user panel
//and deactivate the authentication panel
//HERE IS THE PROBLEM
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}
I'm not trying to load another scene or anything else.
If needed I can provide more information
So my answer is very similar to the accepted answer from Milod's, but a little different, as it took me a while to wrap my head around his, even though his still works.
The Issue:
Normally, all your code runs on a single thread in Unity, since Unity is single-threaded,
however when working with APIs like Firebase, which require callbacks, the callback functions will be handled by a new thread.
This can lead to race-conditions, especially on a single-threaded engine like Unity.
The solution (from Unity):
Starting from Unity 2017.X, unity now requires changes to UI components to be run on the Main thread (i.e. the first thread that was started with Unity).
What is impacted ?:
Mainly calls that modify the UI like...
gameObject.SetActive(true); // (or false)
textObject.Text = "some string" // (from UnityEngine.UI)
How this relates to your code:
public void SignInWithEmail() {
// auth.SignInWithEmailAndPasswordAsyn() is run on the local thread,
// ...so no issues here
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
// .ContinueWith() is an asynchronous call
// ...to the lambda function defined within the task=> { }
// and most importantly, it will be run on a different thread, hence the issue
DatabaseReference.GetValueAsync().ContinueWith(task => {
//HERE IS THE PROBLEM
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}
Suggested Solution:
For those calls which require callback functions, like...
DatabaseReference.GetValueAsync()
...you can...
send them to a function which is set up to run on that initial thread.
...and which uses a queue to ensure that they will be run in the order that they were added.
...and using the singleton pattern, in the way advised by the Unity team.
Actual solution
Place the code below into your scene on a gameObject that will always be enabled, so that you have a worker that...
always runs on the local thread
can be sent those callback functions to be run on the local thread.
using System;
using System.Collections.Generic;
using UnityEngine;
internal class UnityMainThread : MonoBehaviour
{
internal static UnityMainThread wkr;
Queue<Action> jobs = new Queue<Action>();
void Awake() {
wkr = this;
}
void Update() {
while (jobs.Count > 0)
jobs.Dequeue().Invoke();
}
internal void AddJob(Action newJob) {
jobs.Enqueue(newJob);
}
}
Now from your code, you can simply call...
UnityMainThread.wkr.AddJob();
...so that your code remains easy to read (and manage), as shown below...
public void SignInWithEmail() {
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
UnityMainThread.wkr.AddJob(() => {
// Will run on main thread, hence issue is solved
userPanel.SetActive(true);
authPanel.SetActive(false);
})
}
}
}
So basically UI elements need to be modified in Main thread, and I found this script and it will execute your function in Main thread, just put your function in a Coroutine and Enqueue it to the script(UnityMainThreadDispatcher). (You need an object in the scene and add the MainThreadDispathcer script to it)
Here's how my Function looked:
public void SignInWithEmail()
{
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
//Here's the fix
UnityMainThreadDispatcher.Instance().Enqueue(ShowUserPanel());
}
}
}
public IEnumerator ShowUserPanel()
{
uiController.userPanel.panel.SetActive(true);
uiController.authPanel.SetActive(false);
yield return null;
}
This is the script to run it in Main Thead
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class UnityMainThreadDispatcher : MonoBehaviour {
private static readonly Queue<Action> _executionQueue = new Queue<Action>();
/// <summary>
/// Locks the queue and adds the IEnumerator to the queue
/// </summary>
/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
public void Enqueue(IEnumerator action) {
lock (_executionQueue) {
_executionQueue.Enqueue (() => {
StartCoroutine (action);
});
}
}
/// <summary>
/// Locks the queue and adds the Action to the queue
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
public void Enqueue(Action action)
{
Enqueue(ActionWrapper(action));
}
IEnumerator ActionWrapper(Action a)
{
a();
yield return null;
}
private static UnityMainThreadDispatcher _instance = null;
public static bool Exists() {
return _instance != null;
}
public static UnityMainThreadDispatcher Instance() {
if (!Exists ()) {
throw new Exception ("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
}
return _instance;
}
void Awake() {
if (_instance == null) {
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
public void Update() {
lock(_executionQueue) {
while (_executionQueue.Count > 0) {
_executionQueue.Dequeue().Invoke();
}
}
}
void OnDestroy() {
_instance = null;
}
}
Note that Firebase has now a nice ContinueWithOnMainThread extension method that solves this problem more elegantly than the other suggested answers:
using Firebase.Extensions;
public void SignInWithEmail() {
// This code runs on the caller's thread.
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
// This code runs on an arbitrary thread.
DatabaseReference.GetValueAsync().ContinueWithOnMainThread(task => {
// This code runs on the Main thread. No problem.
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}```
In Unity C# script I have a singleton game controller object in which the game variables are stored but I am getting odd behaviour when accessing them in a member function. It prints the initialized value, not the current one. In the update function however it prints the correct value each frame. I summarized the class below. The controller class has a static reference to itself. If you need to know additional details you can ask. I am new to C# and Unity so I might be lacking some obvious answer.
Thanks
public class controller : MonoBehaviour {
public int[] star = new int[64];
void Start(){ /* calls another function to set 0 for each star index */ }
void Update(){ // during gameplay star[0] gets a value of 1
print(star[0]); // prints correct value which is 1
}
public void checkValue(){
print(star[0]); // prints 0 incorrectly which should be 1
}
}
I've set up a little example. I created a button and an empty gameobject GameController. I added this code to the GameController:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour {
public static GameController instance;
[System.NonSerialized] public int[] star = new int[64];
private void Awake()
{
if(instance == null)
instance = this;
else if(instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
private void Start()
{
StartCoroutine(SetAllTo(1));
}
// for simulating some changes during the game
private IEnumerator SetAllTo(int value)
{
yield return new WaitForSeconds(2.0f);
for(int i = 0; i < star.Length; i++)
star[i] = value;
Debug.Log("Setting done");
}
public void PrintFirst()
{
Debug.Log(star[0]);
}
}
Now I added an OnClick event to the button, dragged the GameController gameobject into the slot and picked PrintFirst.
I start the game and click the button once before the Coroutine log and once after and the console gives the following:
0
Setting Done
1
Edit:
The gameobject for the OnClick event must be in the scene, it can't be a prefab in the assets folder.
It seems that's the call to the start function may also occurs in your external calls before the call for ckeckValue
Try debugging by putting breakpoint in each of these functions and you can understand what is going on.
The start function runs once in the beginning of the game and not again.
For that you have pattern - Singleton
The second thing why to call external function to initialize class member? do it in your default contractor.
- C# default constractor already initialize 0 in int[].
if you still what to run Start from other scope in your code make it public (c# makes variable/methods as private by default)
I am currently designing a GUI for a Leap Motion device (although my problem could arise with any type of listener device).
I am running my application in a Windows Form with the following code:
public partial class FormRecord : Form
{
public FormRecord()
{
InitializeComponent();
}
public void UpdateGUI(Frame frame)
{
//get information from captured frame
//update the GUI (specifically labels)
customLabel0.Text = someValueFromFrame.ToString();
}
//this method code was provided by Leap Motion on their website
public static void RunMain()
{
// Create a sample listener and controller
SampleListener listener = new SampleListener();
Controller controller = new Controller();
//Controller is included in Leap.dll (from SDK) and is an instance of controller
// Have the sample listener receive events from the controller
controller.AddListener(listener);
Console.WriteLine("Press Enter to quit...");
Console.ReadLine();
// Remove the sample listener when done
controller.RemoveListener(listener);
controller.Dispose();
}
//This class was also provided in the Leap Motion SDK
class SampleListener : Listener
{
private Object thisLock = new Object();
private void SafeWriteLine(String line)
{
lock (thisLock)
{
Console.WriteLine(line);
}
}
public override void OnInit(Controller controller)
{
SafeWriteLine("Initialized");
}
public override void OnConnect(Controller controller)
{
SafeWriteLine("Connected");
}
public override void OnDisconnect(Controller controller)
{
//Note: not dispatched when running in a debugger.
SafeWriteLine("Disconnected");
}
public override void OnExit(Controller controller)
{
SafeWriteLine("Exited");
}
public override void OnFrame(Controller controller) //
//public void OnFrame(Controller controller)
{
SafeWriteLine("Framed");
//Do something useful with the captured frame
// get the current frame
Frame frame = controller.Frame();
/* This is where my problem occurs */
// attempt to use information from this form and call UpdateGUI(Frame frame)
}
}
}
Basically, the problem is arising because of the difference in static vs. non-static nature of the two classes. For example, my application (Windows Form) is non-static because it is a form and you must be able to create several instances by nature. However, that is why I cannot simply call:
Form aForm = new Form();
aForm.UpdateGUI();
^^This creates a new form. I want to be able to fun the UpdateGUI() method on the original form.
I have also tried
guiForm = ActiveForm;
and calling the label from there
ActiveForm.customlbl0.Text = valueToDisplay;
More generally, I am trying to update a Windows Form based on data received from a Listener. This is causing me problems because I am having to struggle between static and non-static members. I would make it all one class, however I need to implement both form and Listener for the two components. How can I update a display with real-time data from a listener?
Edit: The listener is an implementation from the SDK file "LeapCSharp.Net4.0dll"
I would make the constructor of SampleListener take an instance of your form class. When you create the Listener pass in a reference to the form and store it in a private field. That way you can call methods on it from inside the listener.
Another option would be to make your SampleListener class raise events for each event. You can then hook the events from inside your form.
How about something like:
public partial class FormRecord : Form
{
Controller controller;
public FormRecord()
{
InitializeComponent();
controller = new Controller();
}
public void UpdateGUI()
{
Frame frame = controller.frame();
//get information from captured frame
//update the GUI (specifically labels)
customLabel0.Text = someValueFromFrame.ToString();
}
If you use listeners be aware that each listener callback is called on its own thread. You usually cannot update objects accessed directly by a GUI framework (like Windows Forms, etc) from any thread other than the main thread. (I don't know the best way to do cross-thread communication in Windows forms, though.)