Sleeper (Module)

Sleeper is a simple module that makes it easy to pause and resume your code using coroutines.
It’s perfect for situations where you want to avoid using while or repeat loops to check for conditions, and it’s useful for managing multiple coroutines.

For devs who prefer to copy and paste instead of downloading the model.
--!strict

-- [[Modules]]
local InsertOrder do
	-- [[Types]]
	type InsertOrderClass = {
		["__index"]: InsertOrderClass,
		["__len"]: (self: InsertOrderObject) -> number,
		["New"]: () -> InsertOrderObject,
		["Add"]: (self: InsertOrderObject, Value: any, Index: any) -> (),
		["Remove"]: (self: InsertOrderObject, Index: any) -> (),
		["Iterate"]: (self: InsertOrderObject) -> () -> (any, any),
		["Clear"]: (self: InsertOrderObject) -> (),
		["GetValueWithIndex"]: (self: InsertOrderObject, Index: any) -> any,
		["GetIndexWithPosition"]: (self: InsertOrderObject, Position: number) -> any,
		["Destroy"]: (self: InsertOrderObject) -> ()
	}
	type InsertOrderObject = typeof(
		setmetatable(
			{} :: {
				["_Data"]: {[any]: any},
				["_Indexes"]: {[number]: any}
			},
			{} :: InsertOrderClass
		)
	)

	-- [[Class]]
	local InsertOrderClass = {} :: InsertOrderClass

	-- [[Metamethods]]
	InsertOrderClass.__index = InsertOrderClass
	InsertOrderClass.__len = function(self: InsertOrderObject)
		return #self._Indexes
	end

	-- [[Utils]]
	local function Clear(Table: {[any]: any}): ()
		for Index, Value in pairs(Table) do
			if type(Value) == "table" then
				Clear(Value)
			end
			Table[Index] = nil
		end
	end

	-- [[Constructor]]
	function InsertOrderClass.New(): InsertOrderObject
		return setmetatable({
			_Data = {},
			_Indexes = {}
		}, InsertOrderClass)
	end

	-- Method to add
	function InsertOrderClass.Add(self: InsertOrderObject, Value: any, Index: any): ()
		assert(Index, "Index cannot be nil")
		if not self._Data[Index] then
			self._Indexes[#self._Indexes + 1] = Index
		end
		self._Data[Index] = Value
	end

	-- Method to remove with a index
	function InsertOrderClass.Remove(self: InsertOrderObject, Index: any): ()
		assert(Index, "Index cannot be nil")
		self._Data[Index] = nil
		local Position = table.find(self._Indexes, Index)
		if Position then
			table.remove(self._Indexes, Position)
		end
	end

	-- Method to iterate
	function InsertOrderClass.Iterate(self: InsertOrderObject): () -> (any, any)
		local CurrentIndex = 0
		return function()
			CurrentIndex = CurrentIndex + 1
			local Index = self._Indexes[CurrentIndex]
			if Index ~= nil then
				return Index, self._Data[Index]
			end
			return nil
		end
	end

	-- Method to clear Data and _Indexes
	function InsertOrderClass.Clear(self: InsertOrderObject): ()
		table.clear(self._Data)
		table.clear(self._Indexes)
	end

	-- Method to get a value with a index
	function InsertOrderClass.GetValueWithIndex(self: InsertOrderObject, Index: any): any
		assert(Index, "Index cannot be nil")
		local Value = self._Data[Index]
		return Value
	end

	-- Method to get a index with a position
	function InsertOrderClass.GetIndexWithPosition(self: InsertOrderObject, Position: number): any
		assert(Position, "Position cannot be nil")
		return self._Indexes[Position]
	end

	-- Method to destroy
	function InsertOrderClass.Destroy(self: InsertOrderObject): ()
		Clear(self)
		setmetatable(self, nil)
	end

	-- [[Returning]]
	InsertOrder = InsertOrderClass
end

-- [[Types]]
type SleeperClass = {
	["__index"]: SleeperClass,
	["New"]: () -> SleeperObject,
	["Sleep"]: (self: SleeperObject, Index: any?) -> ... any,
	["WakeUpByIndex"]: (self: SleeperObject, Index: any, ... any) -> (),
	["WakeUpByPosition"]: (self: SleeperObject, Position: number, ... any) -> (),
	["WakeUpAll"]: (self: SleeperObject, ... any) -> (),
	["Destroy"]: (self: SleeperObject) -> ()
}
type SleeperObject = typeof(
	setmetatable(
		{} :: {
			["_RunningCoroutines"]: typeof(InsertOrder.New())
		},
		{} :: SleeperClass
	)
)

-- [[Class]]
local Sleeper = {} :: SleeperClass

-- [[Metamethods]]
Sleeper.__index = Sleeper

-- [[Constructor]]
function Sleeper.New(): SleeperObject
	return setmetatable({
		_RunningCoroutines = InsertOrder.New()
	}, Sleeper)
end

-- Method to yield
function Sleeper.Sleep(self: SleeperObject, Index: any?): ... any
	self._RunningCoroutines:Add(coroutine.running(), Index or #self._RunningCoroutines + 1)
	return coroutine.yield()
end

-- Method to continue a yield
function Sleeper.WakeUpByIndex(self: SleeperObject, Index: any, ...: any): ()
	assert(Index, "Index cannot be nil")
	local RunningCoroutine = self._RunningCoroutines:GetValueWithIndex(Index)
	if not RunningCoroutine then
		warn(`Index {Index} doesn't exist`)
		return
	end
	task.spawn(RunningCoroutine, ...)
	self._RunningCoroutines:Remove(Index)
end

function Sleeper.WakeUpByPosition(self: SleeperObject, Position: number, ...: any): ()
	assert(Position, "Position cannot be nil")
	local PositionIndex = self._RunningCoroutines:GetIndexWithPosition(Position)
	local RunningCoroutine = PositionIndex and self._RunningCoroutines:GetValueWithIndex(PositionIndex)
	if not RunningCoroutine then 
		warn(`Position {Position} doesn't exist`)
		return
	end
	task.spawn(RunningCoroutine, ...)
	self._RunningCoroutines:Remove(PositionIndex)
end

-- Method to continue all yields
function Sleeper.WakeUpAll(self: SleeperObject, ...: any): ()
	for _, RunningCoroutine in self._RunningCoroutines:Iterate() do
		task.spawn(RunningCoroutine, ...)
	end
	self._RunningCoroutines:Clear()
end

-- Method to destroy
function Sleeper.Destroy(self: SleeperObject): ()
	self._RunningCoroutines:Destroy()
	table.clear(self)
	setmetatable(self, nil)
end

-- [[Returning]]
return Sleeper
Features

Sleeper.New()creates a new Sleeper object

SleeperObject:Sleep(index?)pauses the current coroutine, optionally storing it under a custom index

SleeperObject:WakeUpByIndex(index, ...)resumes the coroutine that was paused using the given index

SleeperObject:WakeUpByPosition(position, ...)resumes the coroutine based on the order it was added

SleeperObject:WakeUpAll(...)resumes all paused coroutines

SleeperObject:Destroy()cleans up the object

Examples

Example #1: Wait for Condition

local Sleeper = require(...)
local SleeperObject = Sleeper.New()

local condition = false

task.delay(3, function()
    condition = true
    SleeperObject:WakeUpByIndex("Condition")
end)

SleeperObject:Sleep("Condition")
print(condition) -- true

This replaces code like:

while not condition do task.wait() end

Example #2: Simple Module Loader

local Sleeper = require(...)
local sleepers = {}
local loadedModules = {}

local ModuleLoader = {}

function ModuleLoader.Get(name)
    if not loadedModules[name] then
        sleepers[name] = sleepers[name] or Sleeper.New()
        sleepers[name]:Sleep()
    end
    return loadedModules[name]
end

function ModuleLoader.Init(modules)
    for _, moduleScript in ipairs(modules) do
        local name = moduleScript.Name
        loadedModules[name] = require(moduleScript)
        
        if sleepers[name] then
            sleepers[name]:WakeUpAll()
            sleepers[name]:Destroy()
            sleepers[name] = nil
        end
    end
end

return ModuleLoader
Other Use Cases
  • Queue systems

You can use this module for many other things beyond just these examples. It’s flexible and can be adapted to different situations where you need to manage coroutines.

Is this module good?
  • Yes🥰
  • No😈
0 voters
2 Likes

Isn’t it just coroutines with extra steps? :roll_eyes:

2 Likes

Hey, thanks for your feedback!

You’re right that this module builds on coroutines, but it adds useful features like pausing and resuming specific coroutines by index or position (insert order position). It allows for more control when managing multiple coroutines without manually handling each one, which is especially useful in complex systems.

Hope that clears things up!

2 Likes

Feedback is greatly appreciated!

Is it just me or do people use AI for every reply nowadays?

1 Like

That’s absolutely right; I couldn’t agree more! XD

I asked AI to correct some grammar issues because English is not my first language

local condition = false
local thread = coroutine.running()

task.delay(3, function()
    condition = true
    coroutine.resume(thread)
end)

coroutine.yield()
print(condition) --> true

Is this module really needed? Just use coroutines.

2 Likes

OP simply made an abstraction on coroutines very much; it isn’t particularly bad; it’s a good concept; however, it falls flat somewhat due to it simply being possible with simply using coroutines directly (potentially also being more performant…?), so I’d say OP just needs to add some more things into the library that could be useful.

1 Like

I will add more features (30charlimit) :grinning:

1 Like

image
That’s why i added #1 to the name of the model