- Home /
Loom - initialise per app, per scene, or ?
Normally I "initialise" Loom thus:
Loom.Current.GetComponent<Loom>(); // irrelevant call to kickoff Loom.
I then use it thus ... {by the way, this is an interesting example, actually you can't use Parse.com unless you use Loom. Parse-Unity, which is pretty new, has a blockbuster bug or flaw where it goes to the wrong thread!}
public void _NaiveLoginLoomStyle()
{
ParseUser.LogInAsync("fattie", "1234").ContinueWith(t =>
{
// ( point 'P' - see below )
if (t.IsFaulted || t.IsCanceled)
{ .. password wrong.. }
else
{
// wrong thread, can't do it .... GameObject.Find("cube");
Loom.QueueOnMainThread(()=>
{
Debug.Log("Back on the main thread! Win an iPad! :)");
GameObject cubey = GameObject.Find("cube");
cubey.transform.Translate(0,-2,0, Space.World);
});
// handy note, you can also write
// Loom.QueueOnMainThread(SomeFunctionName);
}
});
return;
}
This is all fine
BUT.............................
I've just realised, I assumed one could do this
Loom.Current.GetComponent<Loom>(); // irrelevant call to kickoff Loom.
once, at application launch time .. but that does not work (unless I screwed-up something else).
in fact, when do you have to "initialise" Loom? Is it at "point P" (see code above). Or once, for the class above? Or once per scene?
Or indeed, should you just do it each time before the QueueOnMainThread() call, or?
Cheers!
As we both seem to have discovered (or maybe it's changed on the Parse side?) you can actually use Coroutines to handle Parse async API calls without worrying about threads.
For anyone interested in how to do this see Fatties answer here: http://answers.unity3d.com/questions/701013/whats-the-difference-between-facebook-sdk-unity-an.html
Right, you're 1000% correct, Hua. After we worked on few more projects, I'd say the "only way to go" is to use the "yield..." form.
It's kind of insane on the Parse front that (a) they haven't fixed that bug yet and (b) the doco still, largely, explains it as if the other way works.
BTW on that link to HWQLearner's question ... you've got to love anyone who calls me "$$anonymous$$r. Fattie" :)
Answer by whydoidoit · Mar 13, 2014 at 11:56 AM
So it "depends" if you ask me. I put a Loom initialization in each scene to stop there being any extraneous calls that get processed from the previous scene. If you DontDestroyOnLoad the component it's attached to (and you actually bother to attach it yourself to a scene component, so you are fully in control) then you can have it run once for the entire application - bearing in mind that it's possible queued actions will attempt to access things that no longer exist.
However, you might want to use this version which is improved to enable clearing of actions between scenes anyway and better memory usage.
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Threading;
using WhyDoIDoIt.LinqLite;
using WhyDoIDoIt;
public class Loom : MonoBehaviour
{
void OnApplicationQuit()
{
_quitting = true;
}
static bool _quitting;
private static Loom _current;
private int _count;
/// <summary>
/// Return the current instance
/// </summary>
/// <value>
///
/// </value>
public static Loom Current
{
get
{
Initialize();
return _current;
}
}
static bool _initialized;
static int _threadId;
void Awake()
{
//gameObject.hideFlags = HideFlags.HideAndDontSave;
}
public static void Initialize()
{
if (!Application.isPlaying || _quitting)
return;
var go = !_initialized;
if (!go && _threadId == Thread.CurrentThread.ManagedThreadId && _current == null)
go = true;
if (go)
{
foreach (var loom in Resources.FindObjectsOfTypeAll(typeof(Loom)).Cast<Loom>())
DestroyImmediate(loom.gameObject);
var g = new GameObject("Loom");
//g.hideFlags = HideFlags.DontSave;
//DontDestroyOnLoad(g);
_current = g.AddComponent<Loom>();
_initialized = true;
_threadId = Thread.CurrentThread.ManagedThreadId;
}
}
void OnDestroy()
{
_actions.Clear();
_delayed.Clear();
if (_current == this)
{
_initialized = false;
}
}
private readonly List<Action> _actions = new List<Action>();
public class DelayedQueueItem
{
public float time;
public Action action;
public string name;
}
private readonly List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
public static void QueueOnMainThread(Action action, string name)
{
QueueOnMainThread(action, 0, name);
}
public static void QueueOnMainThread(Action action, float time, string name)
{
if (!Application.isPlaying)
return;
if (Math.Abs(time - 0) > float.Epsilon || !string.IsNullOrEmpty(name))
{
lock (Current._delayed)
{
DelayedQueueItem existing = null;
if (!string.IsNullOrEmpty(name))
existing = Current._delayed.FirstOrDefault(d => d.name == name);
if (existing != null)
{
existing.time = Time.time + time;
return;
}
var queueItem = Recycler.New<DelayedQueueItem>(-1);
queueItem.name = name;
queueItem.time = Time.time + time;
queueItem.action = action;
Current._delayed.Add(queueItem);
}
}
else
{
lock (Current._actions)
{
Current._actions.Add(action);
}
}
}
/// <summary>
/// Queues an action on the main thread
/// </summary>
/// <param name='action'>
/// The action to execute
/// </param>
public static void QueueOnMainThread(Action action)
{
QueueOnMainThread(action, 0f);
}
/// <summary>
/// Queues an action on the main thread after a delay
/// </summary>
/// <param name='action'>
/// The action to run
/// </param>
/// <param name='time'>
/// The amount of time to delay
/// </param>
public static void QueueOnMainThread(Action action, float time)
{
QueueOnMainThread(action, time, null);
}
/// <summary>
/// Runs an action on another thread
/// </summary>
/// <param name='action'>
/// The action to execute on another thread
/// </param>
public static void RunAsync(Action action)
{
var t = new Thread(RunAction)
{
Priority = System.Threading.ThreadPriority.Normal
};
t.Start(action);
}
private static void RunAction(object action)
{
((Action)action)();
}
// Use this for initialization
void Start()
{
}
readonly Action[] _toRun = new Action[4000];
// Update is called once per frame
void Update()
{
if (Current != this)
{
if (Application.isPlaying)
DestroyImmediate(gameObject);
return;
}
if (!Application.isPlaying)
{
_actions.Clear();
_delayed.Clear();
return;
}
var count = Mathf.Min(_actions.Count, 4000);
lock (_actions)
{
_actions.CopyTo(0, _toRun, 0, count);
if (count == _actions.Count)
_actions.Clear();
else
_actions.RemoveRange(0, count);
}
for (var i = 0; i < count; i++)
{
try
{
_toRun[i]();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
lock (_delayed)
{
count = 0;
for (var i = _delayed.Count - 1; i >= 0 && count < 3999; i--)
{
if (!(_delayed[i].time <= Time.time))
{
continue;
}
_toRun[count++] = _delayed[i].action;
_delayed.RemoveAt(i);
}
}
for (var i = 0; i < count; i++)
{
try
{
_toRun[i]();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
void OnLevelWasLoaded()
{
_actions.Clear();
_delayed.Clear();
}
}
i've gotta feeling you have to "initialise" on component X, to use it on, component X. You can't initialise "once for the whole app" on component A (and A is persistent), and then use on component X. I've got a feeling you have to "initialise" on component X, to use on component X !
Shouldn't have to - the only thing initializing does is ensure that there's a component to receive the delayed actions and thread start requests.
Got it. In that case I'll look in to the specific app further and see if there's a reason I couldn't simply " 'initiailse' once in the preLoad scene" and if I figure it out I'll report back to you as a curiosity!! CHEERS
$$anonymous$$aybe an execution order thing? $$anonymous$$y Loom runs pretty early in the execution order.
Answer by Huacanacha · May 15, 2014 at 12:06 AM
Update: if you just need to handle Parse async API calls you can simply use coroutines... see here for an example.
For those stumbling on this thread while tearing their hair out trying to get the Parse.com backend service API working in Unity... here's my solution! It’s a cut down and re-architected version of Loom purely for dispatching message to the main thread. It will auto-initialise per scene just by adding it to an object in the scene. In theory you can use DontDestroyOnLoad to make it persist between scenes (see discussion above). The original Loom class from UnityGems.com had been giving me get_isPlaying errors when calling QueueOnMainThread.
For me this makes the Parse.com backend API usable within Unity. Just call this within the ContinueWith closure after an API call like FindAsync() ;)
Note: I’m sure there are cases I haven’t thought of here and its not thoroughly tested, so use with caution.
My thanks to whydoidoit (I believe?) for the original!
=== Usage ===
SceneLoom.Loom.QueueOnMainThread(() => {
Debug.Log(“Hello main thread!”);
// Your main thread code here
});
=== SceneLoom.cs ===
using UnityEngine;
using System.Collections.Generic;
using Action=System.Action;
public class SceneLoom : MonoBehaviour
{
public interface ILoom {
void QueueOnMainThread(Action action);
}
private static NullLoom _nullLoom = new NullLoom();
private static LoomDispatcher _loom;
public static ILoom Loom {
get {
if (_loom != null) {
return _loom as ILoom;
}
return _nullLoom as ILoom;
}
}
void Awake() {
_loom = new LoomDispatcher();
}
void OnDestroy() {
_loom = null;
}
void Update() {
if (Application.isPlaying) {
_loom.Update();
}
}
private class NullLoom : ILoom {
public void QueueOnMainThread(Action action) {}
}
private class LoomDispatcher : ILoom {
private readonly List<Action> queuedActions = new List<Action>();
private readonly List<Action> actionsToRun = new List<Action>();
public void QueueOnMainThread(Action action) {
lock (queuedActions) {
queuedActions.Add(action);
}
}
public void Update() {
// Pop the actions from the synchronized list
actionsToRun.Clear();
lock (queuedActions) {
actionsToRun.AddRange(queuedActions);
queuedActions.Clear();
}
// Run each action
for (int i = 0; i < actionsToRun.Count; ++i) {
actionsToRun[i]();
}
}
}
}
Thanks man! I met same thread problem and above Loom solution is insufficient because there is no more Loom and whydoidoit library.
no problem but Loom by @whydoidoit is like the most famous library in all of Unity3D. it's true that the link does not work anymore!