Cord – Coroutine-like module written for the Roblox event scheduler

Cord is a module that emulates the abilities of coroutines but allows you to wait within Cords. It uses Events instead of using coroutines directly, so it works within the scheduler.

Github: Source and Documentation
Roblox Module: 1381006055 (require compatible)


Differences from coroutines in Roblox
  • If the Cord yields, so does the caller. The caller always waits until the Cord calls :yield, returns, or errors.
  • If you want to run a Cord in parallel, you can use the :parallel method instead of :resume.
    • This will make Cord work like a coroutine
    • You’ll have to check cord.state or cord:resumable() to make sure the cord can be resumed. If you try to resume a running or finished Cord, it will error.
    • You can get the arguments passed to :yield or the return values using cord:returned() or cord.outArguments
  • If the Cord errors, so does whatever resumed it.
    • You can change this behavior by providing an ErrorBehavior when you create a Cord. If you do this, you can use Cord.error to check if an error occured.
      • Cord:new(function() end, Cord.WARN) to warn on errors
      • Cord:new(function() end, Cord.NONE) to do nothing on errors
      • Cord:new(func, function(cord) --[[handle error]] end) where the result of the error handler is returned to :resume

Examples

Also available on Github.

-- basics
local cord = Cord:new(function()
	Cord:yield(5)
	Cord:yield(7)
	Cord:yield(9)
end)

print(cord:resume())  -- 5
print(cord:resume())  -- 7
print(cord:resume())  -- 9
-- parameters
local cord = Cord:new(function(i)
	Cord:yield(i)
	Cord:yield(i + 2)
	Cord:yield(i + 4)
end)

print(cord:resume(10))  -- 10
print(cord:resume())  -- 12
print(cord:resume())  -- 14
-- passing things into `:resume`
local cord = Cord:new(function(num1)
	local num2 = Cord:yield(num1)
	local num3 = Cord:yield(num1 + num2)
	return num1 + num2 + num3
end)

-- pass `2` in as `num1`: we get `num1`
print(cord:resume(2))  -- 2
-- pass `3` in as `num2`: we get `num1 + num2`
print(cord:resume(3))  -- 5
-- pass `5` in as `num3`: we get `num1 + num2 + num3`
print(cord:resume(5))  -- 10
-- infinite loops and resume
-- this accumulates numbers: every time you give it a number,
--  it adds your number to its value and returns its value
local cord = Cord:new(function(num)
	while true do
		local nextNum = Cord:yield(num)
		num = num + nextNum
	end
end)

print(cord:resume(2))  -- 2
print(cord:resume(3))  -- 5
print(cord:resume(5))  -- 10
-- syntax sugar: we can make things look nicer!
local accumulate = Cord(function(num)
	while true do
		num = num + Cord:yield(num)
	end
end)

print(accumulate(2))  -- 2
print(accumulate(3))  -- 5
print(accumulate(5))  -- 10
-- cords as loop handlers
local cord = Cord(function()
	for i = 1, 10 do
		Cord:yield(i)
	end
end)

for num in cord do
	print(num)  -- will print 1 through 10
end
-- cords as loop handlers 2
local getNums = function(start, count)
	return Cord:new(function()
		for i = start, count do
			Cord:yield(i)
		end
	end)
end

for num in getNums(10, 20) do
	print(num)  -- will print 10 through 20
end

I recently updated Cord to…

  • Use string enums instead of integers. This will be easier to read.
  • Remove the weird partially-running state that :running could check for. You have to yield the Cord from inside the Cord’s main coroutine
  • Add a :parallel method to run the Cord in parallel.

This uses a new module since its changes are not backwards-compatible. I’ve kept the same Github link, though, and you can look in the commit history for the old version.

5 Likes

Just a small note: is it normal for cord:finished() to return true on STOPPED/RUNNING/PAUSED instead of FINISHED/ERROR?

1 Like

Nope, looks like I had the logic backwards. I’ve updated the model and Github to fix that. Thanks!