Приостановка функции
Добро пожаловать на урок о сопрограммах — последней большой теме Lua. Многие новички никогда не встречали ничего подобного, поэтому начнём не с кода, а с идеи.
Каждая функция, которую вы писали до сих пор, выполняется от начала до конца за один раз: когда вы её вызываете, остальная программа ждёт, пока она закончит. А теперь представьте, что Эмма рассказывает вам о своём дне по частям. Утром она говорит: «Я покормила кур». И уходит работать. Когда вы встречаетесь в полдень, она не начинает заново, а продолжает: «Я полила морковь». Она помнит, где остановилась.
Сопрограмма (coroutine) — это функция, которая умеет то же самое: остановиться посередине, запомнить, где она была (и значения своих локальных переменных), а потом продолжить ровно с этого места. Название означает «подпрограммы, которые работают вместе»: «подпрограмма» — старое слово для функции, а сопрограмма ходит по очереди с остальной программой. Вот день Эммы в виде сопрограммы:
-- День Эммы по шагам
local function emmasDay()
print("Эмма кормит кур.")
coroutine.yield()
print("Эмма поливает морковь.")
coroutine.yield()
print("Эмма продаёт яйца на рынке.")
end
local day = coroutine.create(emmasDay)
print(coroutine.status(day))
coroutine.resume(day)
print("(Солнце поднимается выше.)")
coroutine.resume(day)
print(coroutine.status(day))
coroutine.resume(day)
print(coroutine.status(day))Он выводит:
suspended
Эмма кормит кур.
(Солнце поднимается выше.)
Эмма поливает морковь.
suspended
Эмма продаёт яйца на рынке.
deadВсю работу делают три функции из библиотеки coroutine языка Lua:
coroutine.create(emmasDay)создаёт сопрограмму из функцииemmasDay. Пока она ничего не запускает. Передавать нужно саму функцию, без скобок.coroutine.resume(day)(возобновление) запускает сопрограмму, пока она не дойдёт доcoroutine.yield()или до конца функции. Первыйresumeначинает с начала функции, каждый следующий продолжает сразу после тогоyield, где сопрограмма остановилась.coroutine.yield()внутри функции приостанавливает сопрограмму (передаёт управление), и программа продолжается после тогоresume, который её запустил. («Yield» по-английски значит «уступать», как машина уступает дорогу на перекрёстке.)
coroutine.status(day) показывает, в каком состоянии сопрограмма: suspended (приостановлена: ещё не запускалась или ждёт на yield), running (её код выполняется прямо сейчас, так что увидеть это может только сама сопрограмма) или dead (она дошла до конца функции и продолжить уже не может).
Чем это полезно? В играх полно дел, которые занимают время: торговец Гуго едет из города в город, дверь медленно открывается, Эмма идёт через поле. Игра рисует новую картинку (кадр) много раз в секунду или, если она пошаговая, даёт каждому персонажу ход, и все эти дела должны двигаться вперёд по одному маленькому шагу. С сопрограммой вы пишете такое дело как одну обычную функцию, от начала до конца, а coroutine.yield() ставите там, где нужно дождаться следующего кадра или хода. Такие сценарии вы напишете позже в этом уроке.
Типичные ошибки:
- Вызвать функцию самому,
emmasDay(), или со скобками внутриcoroutine.create(emmasDay()). В обоих случаяхemmasDayзапускается как обычная функция, а не как сопрограмма, и еёyieldнечего приостанавливать. Программа выведетЭмма кормит кур.и остановится с ошибкойattempt to yield from outside a coroutine. В этом сообщении нет номера строки; нужную строку вы найдёте вstack traceback:под ним. - Вызвать сопрограмму как функцию,
day(). Сопрограмма — не функция, а значение особого типа,thread, поэтому вы получитеattempt to call a thread value (local 'day'). Сопрограмма запускается только черезcoroutine.resume.
Задание
- 1
Эмма поливает поле, но после первой грядки хочет передохнуть. Превратите
waterFieldв сопрограмму: поставьтеcoroutine.yield()после полива грядки 1 и после грядки 2 (после грядки 3 не нужно). Замените вызовwaterField()наlocal watering = coroutine.create(waterField), один раз возобновитеwatering, а затем выведитеЭмма делает перерыв.Программа выводит:Грядка 1 полита Эмма делает перерыв. - 2
После перерыва выведите статус сопрограммы после текста
Статус:. Затем ещё дважды возобновитеwateringи снова выведите её статус. Программа выводит:Грядка 1 полита Эмма делает перерыв. Статус: suspended Грядка 2 полита Грядка 3 полита Статус: dead