How to end a looping coroutine in Lua?
Asked Answered
P

2

5

I'm currently working on a game using Roblox (which uses Lua). It is a basically made up of several minigames. At the beginning of each round, all the players in game are put in a table and teleported to an area. That is where the coroutine comes into play. As the round is in progress, I want a coroutine to start. Every second that coroutine checks if the player's health is below zero, and removes them from the currentPlayer table if it is.

Sorry if I am not describing the problem correctly, but the coroutine will not yield. I haven't used coroutines before, so I am probably trying to yield it the wrong way. I know most of you will not be familiar with Roblox, but the Lua syntax is the same.

Can someone please give me an example of how I would end a looping coroutine?

currentPlayers = {}
roundTime = 60

local lookForWinners = coroutine.create(function()
  while coroutine.running do
    wait(1)
    for i, v in pairs(currentPlayers) do
      if v.Character.Humanoid.Health <= 0 then
        table.remove(currentPlayers, v)
      end
    end
  end
end)


while wait() do
  repeat display("Two or more players need to be in the game.", 1) until #_G.plrs > 1 --Ignore, just checks if two+ players are in game.
  display("Picking a map...", 3) pickMap()
  teleport(0, 500, 0)
  coroutine.resume(lookForWinners)
  wait(roundTime)
  print("Round over")
  coroutine.yield(lookForWinners)
end
Plead answered 18/2, 2014 at 19:33 Comment(1)
Thanks for fixing the code, my bad for not posting it correctly.Plead
K
5

Lua is a single-threaded language. Coroutines do not cause functions to execute in parallel.

Coroutines are effectively just a way to make a function that can pause its own execution (using coroutine.yield), that can be resumed from outside (using coroutine.resume). There is no "coroutine.running": there's only one line "running" at any given time.

If Roblox were meant for you to use wait() to jump out of the Lua thread, you would write this as a series of loops that check their condition and then call wait():

local currentPlayers={}
local roundTime = 60

while #_G.plrs > 1 do
  display("Two or more players need to be in the game.", 1)
  wait()
end
display("Picking a map...", 3) pickMap()
teleport(0, 500, 0)

for i=0, roundTime do
  for i, v in pairs(currentPlayers) do
    if v.Character.Humanoid.Health <= 0 then
      table.remove(currentPlayers, v)
    end
  end
  wait(1)
end
print("Round over")

However, this is bad code. (Whenever you write code, let loops with a "wait" function in them serve to indicate that something is being done incorrectly.) You should be using Roblox's Events to handle your game's logic.

  • Check to see if the game should start only when the number of players changes.
  • "Look For Winners" only when a Humanoid's health changes (the HealthChanged event).
  • Run the timer on some kind of timer or interval (don't forget that you'll probably want to end your game early once somebody has won).

Events have many, many advantages over a busy loop; the most visible one will be that your checks occur when the thing they're checking for happens, and not later.

Kurr answered 18/2, 2014 at 20:11 Comment(0)
S
3

I suggest you follow Stuart's advice to use events; this is mostly to provide additional information on what coroutines are to help you use them correctly.

Think of coroutines as functions that may return values, but with a twist: while a "normal" function completes when it executes return, when you yield from a coroutine, it saves its state, so that resume can then continue from the point where you yielded as if nothing happened. Note that you only yield from a coroutine and only to the point where the resume of that coroutine was done (this is no different from calling a function and returning from it; the control returns to the point where you called the function).

In addition to that, resume and yield calls allow you to pass values to the coroutine and return (intermediate) values from the coroutine. See this SO answer for an example of how this can be used.

One can still return from a coroutine and it's no different from returning from a function, which completes its execution. If you check the status of the coroutine at that time (coroutine.status), it should be "dead".

So, to answer your question how you can end a looping coroutine: you can return from it, you can yield() from it (and never resume it again), or you can call error(), which you can then catch and check for in the result of the resume call. Having said that, I agree with Stuart that it may be the wrong way to solve your problem.

Spicer answered 18/2, 2014 at 21:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.