I’m trying to figure out the value in Coroutines? I’ve got a multiplayer game (Colyseus) and I’m currently getting the reconnecting functionality working on reload, and I’m considering if having a heartbeat functionality to reconnect without reloading is worthwhile. I thought this might be something that a Coroutine would handle, but I’m less sure now. Any thoughts are appreciated.
Coroutines provide you with a means of running Lua code over a long period of time outside of the normal flow of execution. You provide a function to be run in the coroutine and you can from within the coroutine pause execution and from the outside resumes it again.
You can use it to synchronise a sequence of asynchronous function calls. Or to run an entire game loop. Or pathfinding over multiple frames. Or the AI logic of individual agents.
I am using coroutines as part of an event system. For example, when the player interacts with an NPC, I want to play a cutscene, show some dialog, disable player input, move characters around, etc.
All of this happens over many frames. I want some of these actions to happen at the same time (scrolling dialog and character movement) and others to happen alone. How do I keep track of everything and coordinate it all, then give input back to the player at the end? By using coroutines, I can pause and resume execution throughout the event!
For example: scrolling dialog box. I want to push one new character to the box every frame. Each cycle of the coroutine pushes one character of a string, then pauses until the next frame’s update().
We use coroutines for scripting in game dialog one line at a time. Doing it this way enables us to use Lua directly between lines to do things like get user input / change variables / do anything we want all in the Lua script without needing to code custom functions for everything. It is powerful and flexible.
True, but coroutines are much more low-cost than script files. And they don’t have to be associated with game objects (although you do need at least one script file to start the coroutine).
And I love the way in which you can make long running and complex tasks look synchronous. With coroutines you can write code that looks like this:
moveTo(position, 200) -- move to position, 200 pixels per second
wait(2) -- wait 2 seconds
fireRocket(target) -- fire rocket at target
wait(1) -- wait 1 second
selfDestruct() -- boom!
A sequence of events that happen over a longer period of time, written one instruction after the other, with no notion of callbacks, delays or anything like that.
The moveTo() and wait() functions looks like this:
local function moveTo(target, speed)
local co = coroutine.running()
local pos = go.get_position()
local distance = vmath.length(target-pos)
local duration = distance / speed
go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, target, go.EASING_LINEAR, duration, 0, function()
coroutine.resume(co)
end)
coroutine.yield()
end
local function wait(delay)
local co = coroutine.running()
timer.delay(delay, false, function()
coroutine.resume(co)
end)
coroutine.yield()
end
I am not myself familiarized with the ins and outs of implementing coroutines.
The “wait(seconds)” seems a bit less useful to me than a wait for the move to to actually complete?
@britzl, how would you recommend that be implemented?
No no, the moveTo() function will not return until the move is finished. Then it will wait another 2 seconds at that location, then fire a rocket, wait another second and finally self destruct.
The two wait() were not put there to wait for the previous commands to complete. Sorry if it looked that way!
function init(self)
self.co = coroutine.create(function()
print("in coroutine")
print("waiting for message")
local message_id = coroutine.yield()
print("received message", message_id)
end)
coroutine.resume(self.co)
end
function on_message(self, message_id, message, sender)
if self.co and coroutine.status(self.co) == "suspended" then
coroutine.resume(self.co, message_id)
end
end