# multi — Lua Cooperative Multitasking Library ### Version 16.3.0 · MIT License · by Ryan Ward --- ## Table of Contents 1. [Overview](#overview) 2. [Core Concepts](#core-concepts) 3. [Getting Started](#getting-started) 4. [The Main Loop](#the-main-loop) 5. [Task Types (Actors)](#task-types-actors) - [newLoop](#newloop) - [newTLoop](#newtloop) - [newAlarm](#newalarm) - [newStep](#newstep) - [newTStep](#newtstep) - [newEvent](#newevent) - [newUpdater](#newupdater) - [newTimer](#newtimer) - [newTimeout](#newtimeout) 6. [Threads](#threads) - [thread.newThread](#threadnewthread) - [thread.sleep / thread.hold](#threadsleep--threadhold) - [thread.yield / thread.skip](#threadyield--threadskip) - [thread.holdFor / thread.holdWithin](#threadholdfor--threadholdwithin) - [thread.newISOThread](#threadnewisothreads) - [thread.newFunction](#threadnewfunction) 7. [Connections (Events)](#connections-events) - [newConnection](#newconnection) - [Fire / Connect / Unconnect](#fire--connect--unconnect) - [Connection Operators](#connection-operators) - [Destroying Connections](#destroying-connections) 8. [Processors](#processors) - [newProcessor](#newprocessor) - [Processor Options](#processor-options) - [Running a Processor](#running-a-processor) 9. [Priority System](#priority-system) 10. [Services](#services) 11. [Tasks (Deferred Work)](#tasks-deferred-work) 12. [Scheduled Jobs](#scheduled-jobs) 13. [Global Variables & Thread Communication](#global-variables--thread-communication) 14. [Type System](#type-system) 15. [Utility Functions](#utility-functions) 16. [Settings & Initialization](#settings--initialization) 17. [System Events](#system-events) 18. [UUID Utilities](#uuid-utilities) 19. [Advanced Patterns](#advanced-patterns) 20. [Quick Reference Card](#quick-reference-card) --- ## Overview `multi` is a cooperative multitasking library for Lua. It provides a structured event loop, coroutine-based threads, typed connections (event emitters), and a rich set of timer and scheduling primitives — all without requiring OS threads or external dependencies. The library is built around a single shared **main loop** that drives every actor (loop, alarm, step, thread, etc.) in turn. Because Lua is single-threaded, all concurrency is *cooperative*: tasks must yield control to let other tasks run. **Key design principles:** - All objects share a common interface (Pause, Resume, Destroy, setPriority, etc.). - Connections decouple event producers from consumers. - Threads are coroutines managed by the scheduler; `thread.sleep` and `thread.hold` yield without blocking the loop. - Processors are isolated sub-schedulers that can be run inside threads or independently. --- ## Core Concepts | Concept | Description | |---|---| | **Actor** | Any object placed in the main loop that has an `Act()` method (loop, alarm, step, etc.). | | **Connection** | An event channel. Producers call `:Fire(...)`, consumers call `:Connect(func)`. | | **Thread** | A coroutine managed by the scheduler. Uses `thread.sleep` / `thread.hold` to yield. | | **Processor** | An isolated scheduler with its own actor list and thread pool. | | **Priority** | A numeric weight controlling how often an actor is executed in priority-mode mainloops. | | **Task** | A one-shot deferred function queued via `:newTask(func)`. | --- ## Getting Started ```lua local multi, thread = require("multi"):init() -- Create a loop that fires every iteration multi:newLoop(function(self, elapsed) print("Elapsed:", elapsed) end) -- Create a timed loop that fires every 1 second multi:newTLoop(function(self, ticks) print("Tick:", ticks) end, 1) -- Start the scheduler (blocks until multi.Stop() is called) multi:mainloop() ``` ### Initializing with Settings ```lua local multi, thread = require("multi"):init({ print = true, -- enable multi.print() output warn = true, -- enable multi.warn() output debugging = false, -- enable multi.debug() output + debug manager error = false, -- if true, hard-errors on runtime errors priority = false, -- enable priority-based scheduling findopt = false, -- enable optimization hints }) ``` `init()` returns `multi` and `thread` — always destructure both. --- ## The Main Loop The main loop drives every actor. There are two variants: ### `multi:mainloop()` Standard round-robin scheduler. Every actor is visited once per loop iteration in reverse insertion order. ```lua multi:mainloop() ``` ### `multi:p_mainloop()` Priority-based scheduler. Actors with higher priority are executed more frequently. Enable it via `init({ priority = true })`. ### `multi:uManager(dt?)` Runs a **single pass** of the loop manually. Useful when embedding `multi` inside another game loop or framework. ```lua -- Inside LÖVE2D update callback: function love.update(dt) multi:uManager(dt) end ``` ### `multi.Stop()` Stops the main loop. ```lua multi.Stop() ``` --- ## Task Types (Actors) All actors are created on `multi` or on a **processor**. Every actor shares these common methods: | Method | Description | |---|---| | `:Pause()` | Suspends the actor (its `Act()` is replaced with a no-op). | | `:Resume()` | Resumes a paused actor. | | `:Destroy()` | Removes the actor from the loop permanently. | | `:setPriority(s)` | Sets the priority. Accepts string or number (see [Priority System](#priority-system)). | | `:setName(name)` | Sets a human-readable name. | | `:isPaused()` | Returns `true` if the actor is paused. | | `:isActive()` | Returns `true` if the actor is active. | --- ### newLoop A loop that fires **every scheduler iteration**. ```lua local loop = multi:newLoop(func?, notime?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `func` | function | nil | Connected to `OnLoop` immediately | | `notime` | boolean | false | If `true`, `elapsed` is always `nil` | **Callback signature:** `function(self, elapsed, dt)` - `self` — the loop object - `elapsed` — seconds since the loop was created (or `nil` if `notime = true`) - `dt` — delta time passed from the scheduler **Connections:** | Connection | Fires When | |---|---| | `OnLoop` | Every scheduler iteration | ```lua local loop = multi:newLoop(function(self, elapsed, dt) if elapsed > 5 then print("5 seconds have passed!") self:Destroy() end end) ``` --- ### newTLoop A **timed loop** that fires at a fixed interval. ```lua local tloop = multi:newTLoop(func?, interval?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `func` | function | nil | Connected to `OnLoop` immediately | | `interval` | number | 0 | Seconds between firings | **Callback signature:** `function(self, ticks, dt)` - `ticks` — total number of times `OnLoop` has fired **Methods:** | Method | Description | |---|---| | `:Set(n)` | Change the interval | | `:Pause()` | Pauses and freezes the internal timer | | `:Resume()` | Resumes and unfreezes the internal timer | ```lua -- Fire every 2 seconds multi:newTLoop(function(self, ticks) print("Tick #" .. ticks) if ticks >= 10 then self:Destroy() end end, 2) ``` --- ### newAlarm A **one-shot timer** that fires after a delay and then pauses itself. ```lua local alarm = multi:newAlarm(seconds?, func?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `seconds` | number | 0 | Delay in seconds | | `func` | function | nil | Connected to `OnRing` immediately | **Connections:** | Connection | Fires When | |---|---| | `OnRing` | When the alarm expires | **Callback signature:** `function(self, dt)` **Methods:** | Method | Description | |---|---| | `:Reset(n?)` | Restarts the alarm; optionally sets a new duration | | `:Pause()` | Pauses (freezes remaining time) | | `:Resume()` | Resumes from where it was paused | ```lua multi:newAlarm(3, function(self) print("3 seconds elapsed!") self:Reset(3) -- reset for another 3 seconds end) ``` --- ### newStep A **counter** that steps through a range of values. ```lua local step = multi:newStep(start?, reset?, count?, skip?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `start` | number | 1 | Starting value | | `reset` | number | math.huge | Ending value (inclusive) | | `count` | number | 1 | Increment per step | | `skip` | number | 0 | Number of loop iterations to skip between steps | **Connections:** | Connection | Fires When | |---|---| | `OnStart` | When the step position is at `start` | | `OnStep` | Every step; receives `(self, position, dt)` | | `OnEnd` | When `position` reaches `reset` | **Methods:** | Method | Description | |---|---| | `:Update(start, reset, count, skip)` | Update parameters and resume | | `:Count(n)` | Change the step increment | | `:Break()` | Hard-stop (sets `Active = nil`) | ```lua multi:newStep(1, 5, 1):OnStep(function(self, pos, dt) print("Step:", pos) end) ``` --- ### newTStep A **timed step** — same as `newStep` but advances on a time interval rather than every loop iteration. ```lua local tstep = multi:newTStep(start?, reset?, count?, interval?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `interval` | number | 1 | Seconds between steps | **Methods:** Same as `newStep`, plus: | Method | Description | |---|---| | `:Set(n)` | Change the interval | | `:Reset(n?)` | Restart and optionally update interval | ```lua -- Count from 1 to 10, one step per second multi:newTStep(1, 10, 1, 1):OnStep(function(self, pos) print("Position:", pos) end) ``` --- ### newEvent An actor that polls a function and fires when it returns a truthy value. ```lua local event = multi:newEvent(task?, func?) ``` | Parameter | Type | Description | |---|---|---| | `task` | function | Polled every iteration; should return a value when "done" | | `func` | function | Connected to `OnEvent` immediately | **Connections:** | Connection | Fires When | |---|---| | `OnEvent` | When `task()` returns a truthy value | **Callback signature:** `function(self, dt)` The return value of `task()` is stored in `self.returns`. **Methods:** | Method | Description | |---|---| | `:SetTask(func)` | Replace the polling function | ```lua local flag = false multi:newEvent(function() return flag end, function(self) print("Flag was set!") end) -- Somewhere else in the code: flag = true ``` --- ### newUpdater An actor that fires on every N-th loop iteration (frame skip). ```lua local updater = multi:newUpdater(skip?, func?) ``` | Parameter | Type | Default | Description | |---|---|---|---| | `skip` | number | 1 | Fire every `skip` iterations | | `func` | function | nil | Connected to `OnUpdate` | **Connections:** | Connection | Fires When | |---|---| | `OnUpdate` | Every `skip` iterations | **Methods:** | Method | Description | |---|---| | `:SetSkip(n)` | Change the skip interval | ```lua -- Fire every 10 iterations multi:newUpdater(10, function(self, dt) print("Every 10 ticks") end) ``` --- ### newTimer A **utility timer** object (not an actor — does not run in the main loop). ```lua local timer = multi:newTimer() ``` **Methods:** | Method | Returns | Description | |---|---|---| | `:Start()` | self | Start (or restart) the timer | | `:Get()` | number | Elapsed seconds | | `:Pause()` | self | Freeze elapsed time | | `:Resume()` | self | Continue from frozen time | | `:isPaused()` | bool | Whether the timer is paused | `:Reset()` is an alias for `:Start()`. ```lua local t = multi:newTimer() t:Start() multi:newLoop(function() if t:Get() > 5 then print("5 seconds passed") t:Stop() end end) ``` --- ### newTimeout Creates a one-shot connection that fires after a delay, then destroys the receiving object. ```lua local timeout = multi:newTimeout(seconds) ``` Returns a connection-modifier function. Used in combination with connection chaining. ```lua -- This pattern pauses self after 5 seconds multi:newTimeout(5)(function(self) print("Timed out!") end) ``` --- ## Threads Threads are coroutines managed by the scheduler. They are created with `thread:newThread` and run cooperatively alongside all other actors. ### thread.newThread ```lua local th = thread:newThread(name?, func, ...) ``` | Parameter | Type | Description | |---|---|---| | `name` | string | Optional display name | | `func` | function | The coroutine body | | `...` | any | Arguments passed to `func` on first resume | **Connections on the returned thread:** | Connection | Fires When | |---|---| | `OnDeath` | Thread function returns normally; receives return values | | `OnError` | Thread function throws an error; receives `(self, errorMsg)` | **Methods:** | Method | Description | |---|---| | `:Pause()` | Pause the thread at its next yield point | | `:Resume()` | Resume a paused thread | | `:Kill()` | Kill the thread at its next yield point | | `:Sleep(n)` | Request the thread sleep for `n` seconds at next yield | | `:Hold(func, opt?)` | Request the thread hold until `func` returns true | | `:getName()` | Returns the thread's name | | `:isPaused()` | Returns `true` if paused | ```lua thread:newThread("MyThread", function(a, b) print("Got:", a, b) thread.sleep(1) print("1 second later") return "done" end, "hello", "world"):OnDeath(function(result) print("Result:", result) -- "done" end) ``` --- ### thread.sleep / thread.hold These are the primary yield mechanisms **inside** threads. #### `thread.sleep(seconds)` Yields the thread for a fixed duration. ```lua thread:newThread("Waiter", function() print("Before sleep") thread.sleep(2) print("After 2 seconds") end) ``` #### `thread.hold(condition, opts?)` Yields until a condition is true. The condition is polled every scheduler pass. ```lua -- Hold until a flag is set local ready = false thread:newThread("Holder", function() thread.hold(function() return ready end) print("Ready!") end) -- Hold until a connection fires local conn = multi:newConnection() thread:newThread("ConnHolder", function() local value = thread.hold(conn) print("Connection fired with:", value) end) -- Hold with a numeric timeout thread:newThread("WithTimeout", function() local result, timeout = thread.hold(function() return someCondition() end, { sleep = 5 }) if multi.isTimeout(timeout) then print("Timed out!") end end) ``` **`opts` table fields:** | Field | Description | |---|---| | `sleep` | Max seconds to wait before returning `nil, TIMEOUT` | | `cycles` | Max loop iterations to wait before returning `nil, TIMEOUT` | | `skip` | Skip N iterations between condition checks | | `interval` | Minimum seconds between condition checks | --- ### thread.yield / thread.skip #### `thread.yield()` Yields for exactly one scheduler pass (minimum possible pause). ```lua thread:newThread("Yielder", function() for i = 1, 1000 do doSomeWork(i) thread.yield() -- give other threads a turn each iteration end end) ``` #### `thread.skip(n)` Yields for exactly `n` scheduler passes. ```lua thread.skip(5) -- pause for 5 iterations ``` --- ### thread.holdFor / thread.holdWithin #### `thread.holdFor(seconds, condition?)` Hold for up to `seconds` seconds, optionally with a condition function. ```lua thread.holdFor(3, function() return isReady() end) ``` #### `thread.holdWithin(cycles, condition?)` Hold for up to `cycles` iterations with an optional condition. ```lua thread.holdWithin(100, function() return isReady() end) ``` --- ### thread.newISOThread Creates an **isolated thread** with its own environment (useful for sandboxing). ```lua local th = thread:newISOThread(name?, func, env?, ...) ``` | Parameter | Type | Description | |---|---|---| | `env` | table | The environment table. `thread` and `multi` are injected unless already present. | ```lua thread:newISOThread("Isolated", function() thread.sleep(1) print("This runs in isolation") end, { print = print }) ``` --- ### thread.newFunction Wraps a function as a **threaded callable** — calling it spawns a thread and optionally waits for its result. ```lua local tfunc = thread:newFunction(func, holdme?) ``` | Parameter | Type | Description | |---|---|---| | `func` | function | The function body | | `holdme` | boolean | If `true`, calling the TFunc blocks until it returns | **Returns a TFunc object.** Calling the TFunc spawns a thread and returns a handle. **Handle methods:** | Method | Description | |---|---| | `:wait()` | Block (or hold in a thread) until the function returns | | `:connect(func)` | Call `func` with the return values when done | | `.OnReturn` | Connection that fires when done | | `.OnError` | Connection that fires on error | | `.OnStatus` | Connection for `thread.pushStatus(...)` values | ```lua local fetchData = thread:newFunction(function(url) thread.sleep(1) -- simulate async work return "data from " .. url end) -- Non-blocking call: local handle = fetchData("http://example.com") handle:connect(function(result) print(result) end) -- Blocking call (holdme = true): local fetchSync = thread:newFunction(function(url) thread.sleep(1) return "sync data" end, true) thread:newThread("caller", function() local result = fetchSync("http://example.com") print(result) end) ``` --- ## Connections (Events) Connections are the event system of `multi`. They decouple event producers from consumers. ### newConnection ```lua local conn = obj:newConnection(protect?, func?, kill?) ``` | Parameter | Type | Description | |---|---|---| | `protect` | boolean | If `true`, each callback is wrapped in `pcall` | | `func` | function | Immediately connected callback | | `kill` | boolean | If `true`, callbacks are removed after first call (one-shot) | --- ### Fire / Connect / Unconnect #### `:Fire(...)` Broadcasts values to all connected callbacks. ```lua local onClick = multi:newConnection() onClick:Fire("button1", 42) ``` #### `:Connect(func, name?)` Subscribes a function. Returns a **connection handle**. ```lua local handle = onClick:Connect(function(button, value) print(button, value) end) ``` Connections can also be subscribed by calling the connection object directly: ```lua onClick(function(button, value) print(button, value) end) ``` **Connection handle methods:** | Method | Description | |---|---| | `:Unconnect()` | Remove this specific subscription | #### `:Unconnect(handle)` Remove a subscription using its handle. ```lua local handle = conn:Connect(myFunc) -- later: conn:Unconnect(handle) ``` #### `:hasConnections()` Returns `true` if there is at least one subscriber. #### `:Lock(conn?)` / `:Unlock(conn?)` Lock/unlock the connection globally (blocking all fires) or per individual subscription handle. ```lua conn:Lock() -- block all subscribers conn:Unlock() -- unblock ``` --- ### Connection Operators Connections support operator overloads for composing event pipelines. #### `+` — OR (merge two connections) ```lua local merged = connA + connB -- fires whenever either connA or connB fires merged(function(...) print("Either fired", ...) end) ``` #### `*` — AND (requires all to fire) ```lua local both = connA * connB -- fires only after both connA AND connB have fired (then resets) both(function(...) print("Both fired") end) ``` #### `%` — Map (transform values) ```lua local mapped = transform_func % sourceConn -- fires with transform_func applied to each emission mapped(function(result) print(result) end) ``` #### `/` — Filter (conditional forward) ```lua local filtered = filter_func / sourceConn -- fires only when filter_func returns truthy (first return value is the guard) filtered(function(...) print("Passed filter", ...) end) ``` #### `..` — Gate / Split ```lua -- Gate: filter_func .. targetConn -- Forwards to targetConn only when filter_func returns true local gated = filter_func .. targetConn -- Split: sourceConn .. sideEffect_func -- Fires both sourceConn and sideEffect_func; returns modified conn local split = sourceConn .. sideEffect_func ``` #### `forwardConnection` Forwards all emissions from one connection to another: ```lua multi.forwardConnection(source, destination) ``` --- ### Destroying Connections ```lua conn:Destroy() -- or conn:destroy() ``` This: - Removes all subscriptions - Recursively destroys all child connections created by operators - Nulls back-references - Replaces methods with no-ops to prevent stale calls --- ## Processors A processor is an isolated scheduler with its own actor list and thread pool. ### newProcessor ```lua local proc = multi:newProcessor(name?, opts?, priority?) ``` **Simple form:** ```lua local proc = multi:newProcessor("MyProc") proc:Start() ``` **With options table:** ```lua local proc = multi:newProcessor("MyProc", { Start = false, -- start immediately? Priority = nil, -- enable priority scheduling? MaxThreads = -1, -- max concurrent threads (-1 = unlimited) MaxObjects = -1, -- max actors (-1 = unlimited) TaskDelay = 0, -- delay between task handler executions Attach = false, -- attach as a loop to parent (auto-driven) TaskHandler = true, -- create the built-in task handler thread }) ``` --- ### Processor Options | Option | Type | Default | Description | |---|---|---|---| | `Start` | boolean | false | Whether to start the processor immediately | | `Priority` | boolean | nil | Enable priority scheduling for this processor | | `MaxThreads` | number | -1 | Thread cap (-1 = unlimited) | | `MaxObjects` | number | -1 | Actor cap (-1 = unlimited) | | `TaskDelay` | number/function | 0 | Delay between deferred task executions | | `Attach` | boolean | false | If true, drives itself as a loop inside the parent | | `TaskHandler` | boolean | true | Whether to spawn the built-in task handler thread | --- ### Running a Processor If `Attach = false`, you must drive the processor manually by calling `proc.run(dt?)` each frame, or inside a thread: ```lua local proc = multi:newProcessor("Worker") proc:Start() thread:newThread("ProcDriver", function() while true do proc.run() thread.yield() end end) ``` If `Attach = true (default)`, a `newLoop` is created on the parent and drives the processor automatically. ```lua local proc = multi:newProcessor("AutoProc", { Attach = true, Start = true }) -- No extra thread needed; proc runs as part of the parent loop ``` **Key methods on a processor:** | Method | Description | |---|---| | `proc:Start()` | Activate the processor | | `proc:Stop()` | Deactivate (still exists, just stops ticking) | | `proc:Destroy()` | Destroy the processor and its attached loop | | `proc:newThread(name, func, ...)` | Spawn a thread inside this processor | | `proc:newFunction(func, holdme?)` | Create a TFunc inside this processor | | `proc:getThreads()` | Returns the thread list | | `proc:getHandler()` | Returns the internal coroutine handler | | `proc:setMaxThreads(n)` | Change the thread cap at runtime | | `proc:setMaxObjects(n)` | Change the object cap at runtime | | `proc:boost(n)` | Run `n` handler passes per `run()` call | | `proc:setTaskDelay(n)` | Change the task delay at runtime | | `proc:isActive()` | Returns `true` if active | | `proc:getFullName()` | Returns `"parent.procName"` | **Creating actors inside a processor:** All standard constructors work on processors: ```lua proc:newLoop(func) proc:newTLoop(func, interval) proc:newAlarm(seconds, func) -- etc. ``` --- ## Priority System Priorities control how frequently an actor is run when `priority = true` is set in `init()` or on a processor. ### Priority Constants | Constant | Value | Description | |---|---|---| | `multi.Priority_Core` | 1 | Runs every iteration (highest) | | `multi.Priority_Very_High` | 4 | Runs every 4 iterations | | `multi.Priority_High` | 16 | Runs every 16 iterations | | `multi.Priority_Above_Normal` | 64 | Runs every 64 iterations | | `multi.Priority_Normal` | 256 | Default | | `multi.Priority_Below_Normal` | 1024 | | | `multi.Priority_Low` | 4096 | | | `multi.Priority_Very_Low` | 16384 | | | `multi.Priority_Idle` | 65536 | Runs very rarely | ### Setting Priority ```lua actor:setPriority("normal") -- by name actor:setPriority("high") actor:setPriority("core") actor:setPriority(multi.Priority_High) -- by constant ``` **Accepted string shortcuts:** `core` / `c`, `very high` / `vh`, `high` / `h`, `above` / `a`, `normal` / `n`, `below` / `b`, `low` / `l`, `very low` / `vl`, `idle` / `i` ### Resolving Priority ```lua print(multi.PriorityResolve[multi.Priority_High]) -- "High" ``` ### Resetting Priority ```lua actor:ResetPriority() -- restore to the value set at creation ``` --- ## Services A service is a priority-managed background thread designed for long-running tasks. ```lua local svc = multi:newService(function(self, data) -- runs continuously while active print("Service running, data:", data) end) svc.Start() ``` **Methods:** | Method | Description | |---|---| | `svc.Start()` | Start the service | | `svc.Stop()` | Stop and clear service data | | `svc.Pause()` | Pause (freeze timer) | | `svc.Resume()` | Resume | | `svc.Destroy()` | Kill thread and stop | | `svc.GetUpTime()` | Seconds since start | | `svc:SetPriority(n)` | Set scheduling priority | | `svc:SetScheme(n)` | Change the sleep/skip scheme (1, 2, or 3) | **Connections:** | Connection | Fires When | |---|---| | `OnStarted` | Service is started | | `OnStopped` | Service is stopped | | `OnError` | Service thread errors | **Schemes:** | Scheme | Behavior | |---|---| | 1 (default) | Uses `thread.sleep` with priority-derived delay | | 2 | Uses `thread.skip` with priority-derived skip count | | 3 | Not yet implemented time based scheme which bases on how long each loop of the service takes --- ## Tasks (Deferred Work) Tasks are one-shot functions queued for execution by the processor's **Task Handler** thread. ```lua multi:newTask(function() print("I run asynchronously in the task queue") end) ``` Or on a specific processor: ```lua proc:newTask(function() print("Task on proc") end) ``` Tasks run in FIFO order. The built-in task handler thread processes them one at a time. **Configuring task delay:** ```lua multi:setTaskDelay(0.1) -- wait 0.1s between tasks -- or a function: multi:setTaskDelay(function() return someCondition() end) ``` --- ## Scheduled Jobs Schedule a function to run when a specific time matches. Uses `os.date` patterns. ```lua multi:scheduleJob(timeTable, func) ``` `timeTable` is a table of `os.date("*t")` fields. The job fires whenever **all** specified fields match the current time. ```lua -- Run at 14:30:00 every day multi:scheduleJob({ hour = 14, min = 30, sec = 0 }, function() print("It's 2:30 PM!") end) ``` The scheduler checks every second using an internal thread. --- ## Global Variables & Thread Communication Threads can communicate via a shared global variables table. ```lua thread.set("myKey", someValue) local v = thread.get("myKey") ``` #### `thread.waitFor(name)` Blocks the current thread until a global variable is set. ```lua thread:newThread("Consumer", function() local value = thread.waitFor("resultReady") print("Got:", value) end) thread:newThread("Producer", function() thread.sleep(2) thread.set("resultReady", 42) end) ``` #### `thread.pushStatus(...)` Fire the `OnStatus` connection of the current thread (or the connection thread that triggered this call). ```lua thread:newThread("StatusPusher", function() thread.sleep(1) thread.pushStatus("halfway done") thread.sleep(1) return "final result" end):OnStatus(function(msg) print("Status:", msg) end) ``` --- ## Type System `multi` uses a simple string-based type registry. ### Registering Types ```lua local myType = multi.registerType("myObject", "myObjects") -- Returns the type string "myObject" -- multi.$MYOBJECT is set as a constant ``` ### Checking Types ```lua obj:isType(multi.registerType("loop")) -- true if obj is a loop multi.hasType("loop") -- returns the registered name or nil multi.isMulitObj(obj) -- true if obj has a registered Type ``` ### Built-in Types | Type String | Access Constant | |---|---| | `"rootprocess"` | `multi.$ROOTPROCESS` | | `"process"` | `multi.$PROCESS` | | `"loop"` | `multi.$LOOP` | | `"tloop"` | `multi.$TLOOP` | | `"alarm"` | `multi.$ALARM` | | `"step"` | `multi.$STEP` | | `"tstep"` | `multi.$TSTEP` | | `"event"` | `multi.$EVENT` | | `"updater"` | `multi.$UPDATER` | | `"timer"` | `multi.$TIMER` | | `"thread"` | `multi.$THREAD` | | `"connector"` | `multi.$CONNECTOR` | | `"service"` | `multi.$SERVICE` | | `"function"` | `multi.$FUNCTION` | | `"timemaster"` | `multi.$TIMEMASTER` | ### Destroyed Objects When an object is fully destroyed via `multi.setType(obj, multi.DestroyedObj)`, all field accesses return a dead sentinel object that silently absorbs all operations. ```lua if getmetatable(obj) == multi.DestroyedObj then print("Object is destroyed") end ``` --- ## Utility Functions ### Logging ```lua multi.print(...) -- prints INFO (only if settings.print = true) multi.warn(...) -- prints WARNING (only if settings.warn = true) multi.debug(...) -- prints DEBUG with traceback (only if settings.debugging = true) multi.error(self?, msg) -- prints ERROR; hard-errors if settings.error = true multi.success(...) -- always prints SUCCESS ``` All use ANSI color codes. ### Math & Tables ```lua multi.Round(num, decimalPlaces?) -- round to N decimal places multi.AlignTable(tab) -- format a 2D table as aligned columns table.merge(t1, t2) -- deep-merge t2 into t1, returns t1 ``` ### Misc ```lua multi.randomString(n) -- returns a random alphanumeric string of length n multi.ForEach(tab, func) -- calls func(tab[i]) for each element multi.timer(func, ...) -- runs func(...), returns elapsed time and return values multi.isTimeout(val) -- true if val is a TIMEOUT sentinel multi.isMulitObj(obj) -- true if obj has a registered multi type os.getOS() -- returns "windows" or "unix" os.sleep(n) -- blocking OS sleep (avoid; prefer thread.sleep) ``` ### Benchmarking ```lua local bench = multi:benchMark(seconds, priority?, label?) bench.OnBench(function(time, steps) print("Completed", steps, "iterations in", time, "seconds") end) ``` ### Getting Active Information ```lua multi.getCurrentProcess() -- returns the currently executing processor multi.getCurrentTask() -- returns the currently executing actor multi:getChildren() -- returns the Mainloop array of this processor multi:getRunners() -- returns non-internal actors in Mainloop multi:getThreads() -- returns the thread list multi:getProcessors() -- returns all registered sub-processors multi:getStats() -- returns a stats table for all processors ``` --- ## Settings & Initialization ```lua local multi, thread = require("multi"):init({ print = false, -- enable multi.print() warn = false, -- enable multi.warn() debugging = false, -- enable multi.debug() and debugManager error = false, -- hard-error on runtime errors priority = false, -- priority-based scheduling findopt = false, -- anonymize function optimization hints }) ``` `init()` can be called multiple times but only applies settings on the first call. Subsequent calls just return `multi` and `thread`. ### Default Settings ```lua multi.defaultSettings -- table defining what the default settings are ``` --- ## System Events These connections are available on the `multi` root object and fire at specific lifecycle points. | Connection | Fires When | |---|---| | `multi.OnObjectCreated` | Any actor is created via `:create()` | | `multi.OnObjectDestroyed` | Any actor is destroyed | | `multi.OnLoad` | Just before the main loop starts (or on first thread creation) | | `multi.OnPreLoad` | Just before each `uManager` pass | | `multi.OnExit` | When `os.exit()` is called | | `multi.OnError` | Global error handler | | `multi.enableOptimization` | When optimization mode is enabled | | `multi.settingsHook` | When `init()` is called with settings | ```lua multi.OnObjectCreated(function(obj, parent) print("Created:", obj.Type, "on", parent.Name) end) multi.OnExit(function(code) print("Exiting with code:", code) end) ``` --- ## UUID Utilities `multi` includes a UUID v7 generator (timestamp-based). ```lua local uuid = multi.generate_uuid7() -- e.g. "018fdb62-1a00-7abc-8def-012345678901" ``` ### Extracting Timestamps ```lua local info = multi.extract_uuid7_timestamp(uuid) -- info.milliseconds -- Unix timestamp in ms -- info.seconds -- Unix timestamp in seconds -- info.date -- "YYYY-MM-DD HH:MM:SS" -- info.iso8601 -- "YYYY-MM-DDTHH:MM:SS.mmmZ" ``` ### Object Creation Timestamps Every actor created via `:create()` receives a UID. The creation timestamp can be retrieved: ```lua local ts = actor:GetCreationTimestamp() -- returns an ISO 8601 string ``` --- ## Advanced Patterns ### Chaining with SetTime / ResolveTimer `SetTime` adds a timeout to any actor. If the actor doesn't call `ResolveTimer` within the specified duration, `OnTimedOut` fires and the actor is paused. ```lua local myLoop = multi:newLoop(function(self, t) if someCondition() then self:ResolveTimer("success") end end) myLoop:SetTime(5) myLoop.OnTimedOut(function(self) print("Timed out after 5 seconds!") end) myLoop.OnTimerResolved(function(self, reason) print("Resolved with:", reason) end) ``` --- ### Reallocating Actors Between Processors An actor can be moved from one processor to another at runtime. ```lua local proc1 = multi:newProcessor("P1"):Start() local proc2 = multi:newProcessor("P2"):Start() local loop = proc1:newLoop(function() print("running") end) -- Move to proc2: loop:reallocate(proc2) ``` --- ### `multi.hold()` Outside Threads `multi.hold()` can be called from outside a thread. It spins the main loop internally until the condition resolves. ```lua -- Block main script execution until a connection fires: local result = multi.hold(someConnection) ``` This is useful for top-level async patterns before `mainloop()` is started. --- ### thread.defer Registers a function to run when the current thread dies or errors. ```lua thread:newThread("WithCleanup", function() thread.defer(function(th) print("Thread died, cleaning up") end) -- do work ... end) ``` --- ### thread.chain Runs a sequence of hold conditions one after another. ```lua thread:newThread("Sequencer", function() thread.chain( function() return conditionA() end, connB, function() return conditionC() end ) print("All three conditions satisfied in order") end) ``` --- ### Optimization Detection When `findopt = true` is passed to `init()`, `multi` detects anonymous functions passed repeatedly to `thread.hold` and emits a warning. ```lua multi.optConn(function(msg) print("OPT HINT:", msg) end) ``` --- ## Quick Reference Card ``` ACTORS (created on multi or processor) multi:newLoop(func?) -- fires every iteration multi:newTLoop(func?, interval?) -- fires every N seconds multi:newAlarm(secs?, func?) -- one-shot after N seconds multi:newStep(s,e,c?,skip?) -- counter s→e by c multi:newTStep(s,e,c?,interval?) -- timed counter multi:newEvent(task?, func?) -- fires when task() is truthy multi:newUpdater(skip?, func?) -- fires every N iterations multi:newTimer() -- utility timer (not an actor) ALL ACTORS SHARE :Pause() :Resume() :Destroy() :setPriority(s) :setName(s) :isPaused() :isActive() :isDone() OnBreak OnPriorityChanged CONNECTIONS conn = obj:newConnection(protect?, func?, kill?) conn:Fire(...) -- emit conn:Connect(func) -- subscribe → handle conn(func) -- shorthand Connect handle:Unconnect() -- unsubscribe conn:Destroy() -- teardown conn + conn2 -- OR merge conn * conn2 -- AND gate func % conn -- map transform func / conn -- filter func .. conn -- gate/split THREADS thread:newThread(name?, func, ...) thread:newFunction(func, holdme?) thread:newISOThread(name?, func, env?, ...) -- inside threads: thread.sleep(n) thread.hold(cond, opts?) thread.yield() thread.skip(n) thread.holdFor(secs, cond?) thread.holdWithin(cycles, cond?) thread.set(k,v) / thread.get(k) / thread.waitFor(k) thread.pushStatus(...) thread.isThread() PROCESSORS proc = multi:newProcessor(name?, opts?) proc:Start() / proc:Stop() / proc:Destroy() proc.run(dt?) -- manual tick proc:newThread(...) -- spawn thread inside proc proc:setMaxThreads(n) proc:boost(n) PRIORITIES "core"/"c" "very high"/"vh" "high"/"h" "above"/"a" "normal"/"n" "below"/"b" "low"/"l" "very low"/"vl" "idle"/"i" UTILITIES multi.isTimeout(v) multi.Round(n, dec?) multi.randomString(n) multi.timer(func, ...) multi:benchMark(secs) multi.generate_uuid7() multi.extract_uuid7_timestamp(uuid) LOGGING (controlled by init settings) multi.print(...) -- INFO (blue) multi.warn(...) -- WARNING (yellow) multi.debug(...) -- DEBUG (white) multi.error(...) -- ERROR (red) multi.success(...) -- SUCCESS (green) ``` --- *Documentation generated for multi v16.3.0-testing.*