feat(stdlib): Jobs, Promises & opt-in Sync concurrency (#14)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 15s
ci / build-and-test (push) Successful in 1m28s
commit-lint / conventional-commits (push) Successful in 2s
docs / build-and-deploy (push) Successful in 23s

A layered concurrency library, safe by default. The recommended tier is
Job.* / Promise.*: a Job is a future — Job.run(kind, arg) starts a
cooperative background compute that advances each Job.pump(budget) and
finishes after enough frames (heavy work spreads out instead of hitching),
or Job.defer + Job.fulfill/fail/cancel drives one by hand. Poll with
done/ok/failed/cancelled, read result/error, count outstanding work with
Job.pending. Promise.all/race combine handle lists into a group job resolved
on the main thread; Promise.count_done/all_done power a loading bar.

The advanced, opt-in Sync.* tier (mutex/atomic/channel + cpu_count) is the
"here be dragons" surface for engine-level message passing.

The whole thing is a deterministic cooperative scheduler: results are
collected on the main thread and a Job never touches the ECS world, so
lockstep and replays stay bit-exact — same jobs + same budget reproduce
byte-for-byte on every target, and a preemptive OS-thread backend can slot
behind this same API later. Ludic has no closures, so a Job carries a
compute kind + int arg (or a hand-driven defer) rather than fn()->…, and
Promise progress is polled rather than chained through then.

Written in Ludic and spliced on demand (like Regex/Dict/Numeric): a program
that never mentions Job.*/Promise.*/Sync.* compiles byte-identically and the
C-free bootstrap fixpoint is untouched. New: runtime/native/jobs.ludic,
emit_ns_call dispatch, parse-time splice, examples/library/jobs.ludic (31
self-asserting checks), 33 docs pages + inventory, changeset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-01 03:32:31 +03:00
parent 872f458cb2
commit 50ecb8472f
44 changed files with 25637 additions and 22806 deletions

View file

@ -0,0 +1,7 @@
---
id: promise
title: Promise
order: 38
---
Combine several <code>Job</code> futures and resolve the group on the main thread. <code>Promise.all</code> succeeds once every member has, <code>Promise.race</code> once the first does; both return an ordinary job handle you poll like any other. For a loading screen, <code>Promise.count_done</code> over the same handles is the bar's numerator and <code>len</code> the denominator, and <code>Promise.all_done</code> is the ready check. Ludic has no closures, so progress is polled rather than chained through a <code>then</code> callback. Build the handle list with <code>new []int</code> + <code>push</code>. Spliced in only when a program mentions <code>Promise.*</code>.

View file

@ -0,0 +1,21 @@
---
id: promise-all
name: Promise.all
category: promise
kind: namespace-method
tokens: Promise.all
sig: Promise.all(handles) -> Job
tip: Succeeds when every member succeeds.
order: 0
ns: Promise
member: all
---
Succeeds when every member succeeds.
```ludic
let hs = new []int
push(hs, a)
push(hs, b)
let loaded = Promise.all(hs)
```

View file

@ -0,0 +1,21 @@
---
id: promise-all_done
name: Promise.all_done
category: promise
kind: namespace-method
tokens: Promise.all_done
sig: Promise.all_done(handles) -> bool
tip: Have all members resolved?
order: 3
ns: Promise
member: all_done
---
Have all members resolved?
```ludic
let hs = new []int
push(hs, a)
push(hs, b)
let ready = Promise.all_done(hs)
```

View file

@ -0,0 +1,21 @@
---
id: promise-count_done
name: Promise.count_done
category: promise
kind: namespace-method
tokens: Promise.count_done
sig: Promise.count_done(handles) -> int
tip: How many members have resolved.
order: 2
ns: Promise
member: count_done
---
How many members have resolved.
```ludic
let hs = new []int
push(hs, a)
push(hs, b)
let progress = Promise.count_done(hs)
```

View file

@ -0,0 +1,21 @@
---
id: promise-race
name: Promise.race
category: promise
kind: namespace-method
tokens: Promise.race
sig: Promise.race(handles) -> Job
tip: Succeeds when the first member does.
order: 1
ns: Promise
member: race
---
Succeeds when the first member does.
```ludic
let hs = new []int
push(hs, a)
push(hs, b)
let first = Promise.race(hs)
```