-1

I am trying to make a Queue of methods which start executing next method once previous method get excuted completely, that queue of methods is working fine for all scenario's except IEnumerator return type one.

Following are list of methods that i am trying to execute one after another.

 public void CallMethod()
{
    
    
    UnityMainThreadDispatcher.Schedule(() =>
    {
        ShowMessage("Minimum!");
    }
    , 0.3f);
    
    Debug.Log("Winner of this round is " + winnerPlayer.GetName());
    UnityMainThreadDispatcher.Schedule(() => 
    {
        scoreboardPopup.ShowPopup();
    }
    , 5.0f);
    
    UnityMainThreadDispatcher.Schedule(() =>
    {
        CardDistributionAnimation.instance.PlayCardDistributionAnimation(false);
    }, 0.5f);

    UnityMainThreadDispatcher.Schedule(() => StartNextRound(),0.5f);
   
    Debug.Log("Call Minimum Completed");
    
}

UnityMainThread Class

public class UnityMainThreadDispatcher : MonoBehaviour
{

    private Queue<IEnumerator> coroutineQueue = new Queue<IEnumerator>();
    public delegate void Task();
    
    
public static void Schedule(Task task, float delay)
    {
        Debug.Log("Task Scheduled " + task.Method);
        //Instance().Enqueue(task, delay);
        Instance().coroutineQueue.Enqueue(DoTask(task, delay));

    }

    private static IEnumerator DoTask(Task task, float delay)
    {
        Debug.Log("Inside DoTask Coroutine");
        yield return new WaitForSeconds(delay);
        task();
    }

    IEnumerator CoroutineCoordinator()
    {
        while (true)
        {
            while (coroutineQueue.Count > 0)
                yield return StartCoroutine(coroutineQueue.Dequeue());
            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;
    }

    private void Start()
    {
        StartCoroutine(CoroutineCoordinator());
    }

}

CoroutineQueue will only start next method in its queue, once previous method got executed succussfully, It is working correctly for all except for below method

CardDistributionAnimation.instance.PlayCardDistributionAnimation(false);

It is happening because PlayCardDistributionAnimation executes on Coroutine method.

PlayCardDistributionAnimation method

 public void PlayCardDistributionAnimation(bool isNewGame)
    {
        
        generateCards(isNewGame);
        StartCoroutine(DistributeCardsToPlayer());
    }

 public void generateCards(bool isNewGame)
    {
        int size = 0;
        if (generatedCards.Count > 0)
            generatedCards.Clear();
        GameObject distributionobject = GameObject.Find("CardDistributionObject");
        if (isNewGame)
            size = playersPosition.Count * 2;
        else
            size = playersPosition.Count;
        for (int i = 0; i < size; i++)
        {
            GameObject vector2 = Instantiate(cardsBack, distributionobject.transform);
            generatedCards.Add(vector2);
            vector2.SetActive(true);
        }

    }
  private IEnumerator DistributeCardsToPlayer()
    {
        Debug.Log("Inside Distribute Cards To Player");
        for(int i=0;i< generatedCards.Count();i++)
        {
            var cover = Instantiate(cardsBack, generatedCards[i].transform.position, Quaternion.identity, generatedCards[i].transform);
            cover.GetComponent<RectTransform>().localScale = Vector3.one;
            var tween = cover.transform.DOMove(playersPosition[i%(playersPosition.Count)].transform.position, 0.5f);
            tween.OnComplete(() => Destroy(cover));
            yield return new WaitForSeconds(0.6f);

        }
        yield return new WaitForSeconds(1f);
        //playersPosition.First().SetActive(true);

        foreach (GameObject gameObject in generatedCards)
            Destroy(gameObject);
        isCardDistributionCompleted = true;
        Debug.Log("Card Distribution method executed succussfully");
    }

I am not sure how i cannot stop other methods to execute, till my Coroutine gets executed completely. I have tried few approaches but none have worked so far, any suggestion would be really helpful.

Chetan Mehra
  • 189
  • 1
  • 3
  • 20
  • 1
    @Thomas what is not reproducible in what OP provided? – derHugo Dec 17 '20 at 11:38
  • @derHugo It's not minimal – Thomas Dec 17 '20 at 11:40
  • @Thomas what would you skip then? To me it looks quite okey .. it has all necessary information to reproduce/understand the exact issue :) I wouldn't say there is way too much information in the code examples .. sure you can also shorten stuff but in this case I'm pretty okey with the amount of information tbh ;) – derHugo Dec 17 '20 at 11:43
  • @Thomas - your comment is unfortunately confusing / incorrect – Fattie Dec 17 '20 at 14:15
  • I can't even follow this code but there is another potential conceptual mega-mistake here. Say (in the abstract) you have a list of "things to do". (Imagine there being "200 of them".) Very often ***you do NOT want to do that*** coroutine-wise, IEnumerator-wise. Don't forget that IEnumerators only happen ***each frame***. It's easy in Unity to naively forget that it's taking a frame to do each thing in a coroutine. But it's a real mistake if you have to mod 200 tanks or whatever and you do it one-a-frame. Some things should be done one-a-frame, but many things not. – Fattie Dec 17 '20 at 18:16

2 Answers2

1

First of all your delegate Task is pretty confusing .. there is already a Task class which is for running async tasks.

It is also pretty unnecessary since there is already a delegate type for a parameter less void: Action ;)

And then I would create an overload which simply takes coroutines as well like

public class UnityMainThreadDispatcher : MonoBehaviour
{
    private Queue<IEnumerator> coroutineQueue = new Queue<IEnumerator>();

    public static void Schedule(IEnumerator routine, float delay)
    {
        Instance().coroutineQueue.Enqueue(DoTask(routine,delay));
    }
    
    public static void Schedule(Action task, float delay)
    {
        Debug.Log("Task Scheduled " + task.Method);
        //Instance().Enqueue(task, delay);
        Instance().coroutineQueue.Enqueue(DoTask(task, delay));

    }

    private static IEnumerator DoTask(Action task, float delay)
    {
        Debug.Log("Inside DoTask Coroutine");
        yield return new WaitForSeconds(delay);
        task();
    }

    private static IEnumerator DoTask(IEnumerator task, float delay)
    {
        Debug.Log("Inside DoTask Coroutine");
        yield return new WaitForSeconds(delay);
        yield return StartCoroutine(task);
    }

    private IEnumerator Start()
    {
        while (true)
        {
            while (coroutineQueue.Count > 0)
                yield return StartCoroutine(coroutineQueue.Dequeue());

            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;
    }
}

And then rather make your method

public void PlayCardDistributionAnimation(bool isNewGame)
{ 
    StartCoroutine(PlayCardDistributionAnimationRoutine(isNewGame));
}

public IEnumerator PlayCardDistributionAnimationRoutine(bool isNewGame)
{   
    generateCards(isNewGame);
    yield return DistributeCardsToPlayer();
}

and for the scheduled version use the routine instead

UnityMainThreadDispatcher.Schedule(
    CardDistributionAnimation.instance.PlayCardDistributionAnimationRoutine(false), 
    0.5f
);

It is a bit funny though that you took the main thread dispatcher - which is actually used to dispatch async stuff from other threads back into the Unity main thread - and turned it into something completely different from that ^^


Note: Typing on smartphone but I hope the idea gets clear

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • I am planning to create a Queue class something like this https://www.jacksondunstan.com/articles/3241 , and use it unity Main Thread class, Unity Thread class, i imported from https://github.com/PimDeWitte/UnityMainThreadDispatcher – Chetan Mehra Dec 17 '20 at 12:10
  • getting Method name expected error for yield return task(); line in DoTask(IEnumerator task, float delay) method – Chetan Mehra Dec 17 '20 at 13:04
  • Should have been without the `()` I guess ^^ only typing on smartphone sorry ^^ – derHugo Dec 17 '20 at 13:54
  • Already tried that, not working, also want to confirm, that should we call PlayCardDistributionAnimationRoutine in action parameter schedule or Ienumerator parameter schedule? – Chetan Mehra Dec 17 '20 at 14:08
-2

The approach in the question is completely wrong, and will never work.

(Task and Action already exist in Unity as explained in the other answer.)

Google many articles, eg https://www.jacksondunstan.com/articles/4926

Note too that you may be drastically misunderstanding the nature of dealing with "threads and stuff" in a game engine ... here's the Famous Thermometer Example™ https://stackoverflow.com/a/54184457/294884

(Please note in that final important sentence: as I say, OP may be drastically misunderstanding the nature of dealing with threads/UX/"etc" in a game engine. I suspect that is so based on the question. Again, "may be".)

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • I don't really share that opinion. The approach is not completely wrong, just not ready ;) And yes `System.Threading.Tasks.Task` is a type that already exists. There speaks nothing against having that own `Task` delegate in your namespace .. it is just extremely misleading ;) OP isn't trying to use multi threading at all ^^ he is just abusing that MainThreadDispatcher for something completely different namely scheduling actions with certain delays ^^ – derHugo Dec 17 '20 at 17:40
  • I strongly and extremely agree with every single sentence in that comment - other than the first sentence!!!! :) :) @derHugo – Fattie Dec 17 '20 at 18:09