[EXP] Unity 3d - Отложенные действия. Invoke VS Coroutines.

Довольно часто в играх существует необходимость выполнять какие-либо действия отложенным образом, в том числе и по времени. Для этого в Unity 3d предусмотрены методы:

• Invoke(string methodName, float time);

• InvokeRepeating(string Method Name, float time, float repeatRate);

• CancelInvoke(); и CancelInvoke(string methodName);

Invoke полагается на Time.timeScale. То есть, если мы вызываем Invoke("SomeMethod", 2f) при Time.timeScale = 0.5, то SomeMethod() начнет выполняться через 4 секунды, а при 0 - не начнет выполняться вовсе.

Не одним Invoke-ом сыты, ведь есть еще способы отложено выполнить действия, например таймеры или корутины. Вот на них, я пожалуй и остановлюсь.

Корутины в данном контексте мне всегда казались очень производительными, практичными и более знакомыми с точки зрения: "А что под капотом?", но не столь удобными в использовании, как тот же Invoke, ведь приходится жертвовать парой строчек кода... А ещё, корутины решают проблему с отложенным запуском при различных значениях Time.timeScale.

Вот как выглядит отложенный запуск с использованием Coroutines:

float _time = 2f; Coroutine _coroutine; private void Start() = > _coroutine = Start(SomeCoroutine()); public IEnumerator SomeCoroutine() { yield return new WaitForSeconds(_time); //Ожидание в условиях Time.timeScale //TO DO 1 yield return new WaitForSeconds(_time); //TO DO 2 yield return new WaitForSecondsRealtime(_time); //Ожидание в реальном времени //TO DO 3 и таким образом можно создать цепочку действий } public void StopMyCoroutine() => StopCoroutine(_coroutine); //Останавливает указанную корутину public void StopAllMyCoroutines() => StopAllCoroutines(); //Останавливает все корутины

Вот так может выглядеть использование Invoke:

float _time = 2f; private void Start() => Invoke(nameof(SomeMethod), _time); private void SomeMethod() { //TO DO 1 Invoke(nameof(SomeMethod2), _time); //типо цепочка действий } private void SomeMethod2() { //TO DO 2 } public void CancelMyInvoke() => CancelInvoke(nameof(SomeMethod)); //отменяет указанный Invoke public void CancelAllInvokes() => CancelInvoke(); //отменяет все Invoke

Но, возвращаясь к изначальной теме, кто круче то? В этом вопросе я учитываю такие критерии как:
• Производительность
• Удобство
• Гибкость

Я думаю, некоторые выводы об удобстве и гибкости сделаны (о них чуть позже). А вот с производительностью дела обстоят по-интереснее...

Тестирование производительности

Для тестирования я не придумал ничего лучше, как создать 5940 сущностей, которые одновременно будут вызывать отложенное действие через Invoke и Coroutine, и наблюдать за этим в профайлере.

Код.
Код.
Армия тёмных сущностей.
Армия тёмных сущностей.
Результат 1.
Результат 1.
Результат 2. WaitForSecondsRealtime в Coroutine.
Результат 2. WaitForSecondsRealtime в Coroutine.

Из тестов можно сделать вывод, что Invoke оказался производительней... А WaitForSecondsRealtime вносит свою лепту в общую картину.

Заключение

Учитывая всё выше сказанное, я склоняюсь к тому, что Invoke более прост в использовании и, как оказывается, производительней, а корутины более многозадачные и гибкие.
Лично мне отдавать предпочтение чему-то одному не приходится - всегда стоит исходить из ситуации и решаемой задачи. Для простых задач можно использовать Invoke, а для более трудных, требующих гибкого подхода можно использовать Coroutine, жертвуя малой каплей производительности.

Надеюсь, статья была вам интересна и полезна. Если у вас есть иной личный опыт использования Invoke и Coroutine, будет интересно узнать что-то новое. Всем мир)

P.S.: Первая статья на DTF. Если у вас есть какие-либо пожелания, оценка или совет формата подачи, я открыт к критике.

44
5 комментариев