Last active
November 26, 2025 23:38
-
-
Save artfwo/e8b9bd51e0043a54b0c0d65d75c332d6 to your computer and use it in GitHub Desktop.
Minimal example of scheduling async tasks in pure Lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- Minimal example of scheduling async tasks in pure Lua | |
| local Future = {} | |
| Future.__index = Future | |
| function Future.new() | |
| local future = setmetatable({}, Future) | |
| future.done = false | |
| future.result = nil | |
| future.waiters = {} | |
| return future | |
| end | |
| function Future:set_result(result) | |
| if self.done then | |
| return | |
| end | |
| self.done = true | |
| self.result = result | |
| for _, co in ipairs(self.waiters) do | |
| local success, err = coroutine.resume(co, result) | |
| if not success then | |
| error(err) | |
| end | |
| end | |
| self.waiters = {} | |
| end | |
| function Future:__tostring() | |
| if self.done then | |
| return string.format("<Future: done (%s)>", tostring(self.result)) | |
| else | |
| return "<Future: pending>" | |
| end | |
| end | |
| local function await(future) | |
| if future.done then | |
| return future.result | |
| end | |
| local co = coroutine.running() | |
| assert(co, "await() must be called from inside a coroutine") | |
| table.insert(future.waiters, co) | |
| return coroutine.yield() | |
| end | |
| local function run(fn) | |
| local future = Future.new() | |
| local co = coroutine.create(function() | |
| local result = fn() | |
| future:set_result(result) | |
| end) | |
| local success, err = coroutine.resume(co) | |
| if not success then | |
| error(err) | |
| end | |
| return future | |
| end | |
| -- Scheduler -- | |
| local TestScheduler = {} | |
| TestScheduler.__index = TestScheduler | |
| function TestScheduler.new() | |
| local test_scheduler = setmetatable({}, TestScheduler) | |
| test_scheduler.time = 0 | |
| test_scheduler.futures = {} | |
| return test_scheduler | |
| end | |
| function TestScheduler:schedule_future_at_time(tick, future) | |
| self.futures[tick] = self.futures[tick] or {} | |
| table.insert(self.futures[tick], future) | |
| end | |
| function TestScheduler:tick() | |
| self.time = self.time + 1 | |
| local futures = self.futures[self.time] | |
| if futures then | |
| for _, future in ipairs(futures) do | |
| future:set_result(self.time) | |
| end | |
| self.futures[self.time] = nil | |
| end | |
| end | |
| local test_scheduler = TestScheduler.new() | |
| local function sleep(ticks) | |
| local future = Future.new() | |
| test_scheduler:schedule_future_at_time(test_scheduler.time + ticks, future) | |
| return future | |
| end | |
| -- Example task | |
| local function test_task() | |
| local b | |
| run(function() | |
| await(sleep(7)) | |
| print("at 7 1 1", test_scheduler.time) | |
| end) | |
| run(function() | |
| await(sleep(7)) | |
| print("at 7 2 1", test_scheduler.time) | |
| await(sleep(1)) | |
| print("at 7 2 2", test_scheduler.time) | |
| end) | |
| print(test_scheduler.time, "task A", tostring(b)) | |
| b = await(sleep(5)) | |
| print(test_scheduler.time, "task A", tostring(b)) | |
| b = await(sleep(5)) | |
| print(test_scheduler.time, "task A", tostring(b)) | |
| await(run(function() | |
| print(test_scheduler.time, "subtask B", tostring(b)) | |
| b = await(sleep(3)) | |
| print(test_scheduler.time, "subtask B", tostring(b)) | |
| end)) | |
| b = await(sleep(3)) | |
| print(test_scheduler.time, "task A", tostring(b)) | |
| end | |
| run(test_task) | |
| repeat | |
| test_scheduler:tick() | |
| until test_scheduler.time > 20 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment