Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Threading - forcing to wait till the change of variable to true.

Discussion in 'Scripting' started by pawelekezg, Oct 16, 2021.

  1. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    Hello.
    I am making a game in which a player has to write a code in order to move the tank (shoot, drive, etc.).
    I am using Roslyn Compiler to run an Interpreter (https://www.codemag.com/article/1607081/How-to-Write-Your-Own-Programming-Language-in-C) which interprets the code written by player and run functions written in Unity. I am facing a problem where i need the interpreter to wait before continuing its execution.
    For example:
    I have a function "ruch(number)" which stands for "move(number)" and it calls prepared in unity function which moves a tank based on the number (0-5 => which represents NE, E, SE, SW, W, NW). So I write the script
    Code (CSharp):
    1. ruch(1);
    and run it with
    Code (CSharp):
    1. CSharpScript.RunAsync(code, ScriptOptions.Default.WithImports("System", "UnityEngine", "SplitAndMerge").AddReferences(typeof(UnityEngine.Transform).Assembly, typeof(SplitAndMerge.Program).Assembly));
    Then the interpreter "plays" ruch(1); method which is implemented:
    Code (CSharp):
    1. protected override Variable EvaluateAsync(string data, ref int from)
    2.         {
    3.             //Variable is defined as static in the class where this method is placed in.
    4.             ifMovementFinished = false;
    5.            
    6.             Debug.LogWarning("Interpreter Thread: " + Thread.CurrentThread.ManagedThreadId);
    7.          
    8.             Variable arg1 = Parser.LoadAndCalculate(data, ref from, Constants.END_ARG_ARRAY);
    9.  
    10.             GameObject gameOb = GameObject.FindWithTag("PlayersScriptHandler");
    11.  
    12.             Dictionary<int, bool> param = new Dictionary<int, bool>();
    13.             param.Add((int)arg1.Value, false);
    14.             gameOb.SendMessage("makeMovementAsync", param);
    15.  
    16.             if (!param[0])
    17.             {
    18.                 Debug.LogError("You can not go through the wall.");
    19.             }
    20.             else
    21.             {
    22. //Here it should be waiting for the ifMovementFinished variable to become true;
    23.             }
    24.             ifMovementFinished = false;
    25.             return Variable.EmptyInstance;
    26.         }
    27.  
    As you can see this function calls makeMovementAsync on a gameobject with tag. It is implemented:
    Code (CSharp):
    1.  public void makeMovementAsync(Dictionary<int, bool> param)
    2.     {
    3.         Debug.LogWarning("Main Thread: " + Thread.CurrentThread.ManagedThreadId);
    4.  
    5.         param[0] = GameObject.Find("GameUI").GetComponent<HexGameUI>().setCurrentCell(param.FirstOrDefault(entry => EqualityComparer<bool>.Default.Equals(entry.Value, false)).Key);
    6.         if (param[0]) // it means that setCurrentCell has determined the tank can move to this place and it started moving it;
    7.         {                  
    8.             StartCoroutine(CR_GetMove());
    9.         }
    10.        
    11.     }
    12.  
    13.  IEnumerator CR_GetMove()
    14.     {
    15.         Thread thread = new Thread(new ThreadStart(async () =>
    16.         {
    17.             Debug.LogWarning("Thread waiting for movFinish: " + Thread.CurrentThread.ManagedThreadId);
    18.             while (!checkIfMovementFinished())
    19.             {
    20.                 await Task.Delay(1);
    21.             }
    22.             UnRuch.setMovementFinished(true); // it calls Interpreter's function to change ifMovementFinished to true;
    23.         }));
    24.         thread.IsBackground = true;
    25.         thread.Start();
    26.  
    27.         while (thread.IsAlive) yield return null;
    28.  
    29.         yield break;
    30.     }
    31.  
    32.  
    I need it to wait till the bool to come true, because it is when the tank "stops" moving. Not waiting for it to finish causes that if my script was ruch(1); ruch(1); there would be an effect like only one instruction was called because when the second one would be in "progress" the position of tank still wouldn't be changed by first instruction. Same would happen if i wrote ruch(1); shoot(2); ~ in the future.
    As you can see i really need to wait for the change of bool variable. I tried many things - semaphores, creating new threads, but each time it was a failure - it either didn't wait for the change or i got deadlock - since both main unity thread and interpreter ran by CSharpScript.RunAsync() works on the same thread. Trying to run it on seperate one causes that interpreter can't find objects from the game. Moreover i can't try await instruction in interpreter's function because it is overriding a method and I can't change method signature.
    You guys are my last hope to get that work. :)

    Edit:
    Simply (I really doubt it is possible) I'd like to tell the thread of interpreter and unity that he shouldn't proceed with interpreter unless the corutine has set the bool to true.
     
    Last edited: Oct 16, 2021
  2. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
    becouse your interpreter works in main thread but you need to wait to en of some action that take multiple frames you need to make something async.
    I see few options:
    1. change EvaluateAsync to async. but it will work only if you could change base class.
    2. move interpreter to separate thread but use some kind of dispatch to not call unity methods from that thread.
     
    Kurt-Dekker likes this.
  3. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    I can't change EvaluateAsync to async because async methods cant have ref parameters. :/
    So I have to choose option 2, right? Is it even possible though? I'd need to tell THREAD_1 (Unity) from THREAD_2 to run function on THREAD_1. :/
     
    Last edited: Oct 16, 2021
  4. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
    About 1. if you really could change EvaluateAsync signature you could change it to not use ref. Like this:
    protected override Task<(Variable Variable, int From)> EvaluateAsync(string data, int from)

    About 2. Yes, you can.
    Simplest implementation I could think about
    Code (csharp):
    1.  
    2. public class DispatchHelper
    3. {
    4.     private static SynchronizationContext _mainThreadContext;
    5. // RuntimeInitializeOnLoadMethod always runs on main thread and unity main thread always have SynchronizationContext
    6.     [RuntimeInitializeOnLoadMethod]
    7.     private static void Init() => _mainThreadContext = SynchronizationContext.Current;
    8.  
    9. // Send wait while action will finish it work on main thread
    10.     public static void Dispatch(Action action) => _mainThreadContext.Send(s => ((Action) s)(), action);
    11. }
    12.  
    13. ...............
    14.         protected override Variable EvaluateAsync(string data, ref int from)
    15.         {
    16.             //Variable is defined as static in the class where this method is placed in.
    17.             ifMovementFinished = false;
    18.        
    19.             Debug.LogWarning("Interpreter Thread: " + Thread.CurrentThread.ManagedThreadId);
    20.      
    21.             Variable arg1 = Parser.LoadAndCalculate(data, ref from, Constants.END_ARG_ARRAY);
    22.  
    23.             Dictionary<int, bool> param = new Dictionary<int, bool>();
    24.             param.Add((int)arg1.Value, false);
    25.  
    26.            DispatchHelper.Dispatch(() =>
    27.            {
    28.                 GameObject gameOb = GameObject.FindWithTag("PlayersScriptHandler");
    29.                 gameOb.SendMessage("makeMovementAsync", param);
    30.             });
    31.          
    32.             if (!param[0])
    33.             {
    34.                 Debug.LogError("You can not go through the wall.");
    35.             }
    36.             else
    37.             {
    38.                 // wait
    39.             }
    40.             ifMovementFinished = false;
    41.             return Variable.EmptyInstance;
    42.         }
    43.  
    44.  
     
    Last edited: Oct 16, 2021
  5. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    I really appreciate your post, but i will try it tomorrow. My mind is exploding after "fighting" with it for several hours. ^^
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Instead of a bool, use a WaitHandle:
    https://docs.microsoft.com/en-us/dotnet/api/system.threading.waithandle?view=net-5.0

    I personally would specifically use a AutoResetEvent (inherits from EventWaitHandle->WaitHandle):
    https://docs.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-5.0

    Instead of ifMovementFinished being a boolean. Make it a AutoResetEvent:
    Code (csharp):
    1. private AutoResetEvent ifMovementFinished = new AutoResetEvent(false);
    Then your code would be like:
    Code (csharp):
    1.         protected override Variable EvaluateAsync(string data, ref int from)
    2.         {
    3.             ifMovementFinished.Reset(); //reset event to make sure it'll block when you call WaitOne
    4.  
    5.             Debug.LogWarning("Interpreter Thread: " + Thread.CurrentThread.ManagedThreadId);
    6.  
    7.             Variable arg1 = Parser.LoadAndCalculate(data, ref from, Constants.END_ARG_ARRAY);
    8.  
    9.             GameObject gameOb = GameObject.FindWithTag("PlayersScriptHandler");
    10.  
    11.             Dictionary<int, bool> param = new Dictionary<int, bool>();
    12.             param.Add((int)arg1.Value, false);
    13.             gameOb.SendMessage("makeMovementAsync", param);
    14.  
    15.             if (!param[0])
    16.             {
    17.                 Debug.LogError("You can not go through the wall.");
    18.             }
    19.             else
    20.             {
    21.                 ifMovementFinished.WaitOne();
    22.             }
    23.             return Variable.EmptyInstance;
    24.         }
    And your setMovementFinished should actually call the 'Set' method of the wait handle:
    Code (csharp):
    1. private void setMovementFinished()
    2. {
    3.     ifMovementFinished.Set();
    4. }
    ...

    Note that this assume EvaluateAsync is NOT on the main thread. Otherwise WaitOne will block the main thread. I'm guessing it is its own thread because of your LogWarning there checking the thread id. But I don't see where in your code you actually spin up threads... so you need to make sure you work that into your logic.

    If you go this route and your program locks up in a deadlock... well we'll need to see all your involved code for spinning up threads and how you deal with threading in general.
     
    Last edited: Oct 16, 2021
  7. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    It was/is running on the same thread indeed. You didn't get my point. I used Debug.LogErrors to see whether the threads that unity and interpreter run were the same.
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    If you're on the same thread you're going to have to break out into more than just this function. There's no line of code you can put in that else to wait.

    1) start using threads, tasks, some asynchronous helper

    2) start using coroutine to wait and yield until ifMovementFinished is true

    3) have an Update loop that waits.

    ...

    From your current code a Coroutine would be the easiest to refactor into. Just change EvaluateAsync into a coroutine (or make it start up a coroutine). And then in that else statement just put:

    Code (csharp):
    1. while(!ifMovementFinished) yield return null;
     
  9. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    1) I couldn't use threads since both unity and interpreter ran on the same thread because I didn't know how can I make "outer thread" call Unity functions (the way VolodymyrBS suggested).
    2) Can't start coroutine in outer .dll
    3) Its outer .dll so "Update()" wouldn't work ~I think so ^^
     
  10. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    I couldn't wait to check this solution and adding while(!ifMovementFinished)Thread.Sleep(50); made my code work as i intended it to be. I really appreciate your help! You're my savior :D
     
    Last edited: Oct 16, 2021
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Well, you can't block mid method with out some sort of asynchronicity going on (think like the async/task library, or threads).

    Unity hacks a way around this with Coroutines exploiting iterator functions to simulate asynchronous behaviour via the yield statement.

    You'll need to fashion something along these lines in your dll. Easiest will likely be the Task library with a synchronization context to the unity thread. Again, since I don't know the design of your code this isn't some straight forward thing. What you'll need to do though is have an entry point in your dll to start this whole process and hand it said synchronization context... then this method in question, EvaluateAsync, can await the coroutine at this point.

    This is almost what VolodymyrBS does... but from the look of their code it won't wait for ifMovementFinished to be true, it just sends the message. There's nothing in their that waits until your coroutine in makeMovementAsync finishes. You'd need to add logic to his method (where he comments you don't need to wait) to await ifMovementFinished.

    ...

    Alternatively if you don't like async/task stuff, you could alternatively in the entry point of your dll pass your code a GameObject it can hook into for coroutines/update.


    [edit] It looks like while I was typing you got VolodymyrBS's solution to work using a Thread.Sleep... though I will point out that this implies you're on a different thread. The fact you're not aware of that, combined with how async/tasks works, you may want to be careful. This could potentially cause a deadlock. Alternatively you could use SemaphoreSlim.WaitAsync similar to WaitHandle.WaitOne.
     
    Last edited: Oct 16, 2021
  12. pawelekezg

    pawelekezg

    Joined:
    Nov 7, 2019
    Posts:
    12
    I am and was aware about "thread situation". At first when i created a post i was having both unity and interpreter ran on same thread => which i checked by printing ids. Later on when VolodymyrBS posted I did change interpreter to be run on new thread and therefore i could use Sleep() on it ;)