feat: prebuilt binary packages — closed-source functions/systems/components over the reflection ABI #64
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Follow-up to #63. Source packages compile into the consumer (AOT, first-class compile-time ECS) — shipped. This issue adds prebuilt binary packages: a developer ships a compiled artifact and a consumer uses its exported functions, systems and components without the source, resolved through the stable reflection C-ABI.
Decision (chosen over a source-recompile hybrid): full binary via the reflection ABI — true closed-source machine-code modules. This is native-only (no wasm/cross-compile), second-class ECS (by-name dynamic components, an indirect call per registered system), and not deterministic-first-class — the documented escape hatch, exactly as the controllers roadmap (#57–#62) framed binary distribution.
Model
A binary module is a
.dylibthat, at load, registers its components and systems against the host's world through the C-ABI and exposes its functions as symbols the consumer binds:ludic_register_prop(name, nfields)(already exists, EV7) +world_get/set/has/attach_dyn. A module declares its components dynamically; the host owns the single world.ludic_register_system(fn, phase)+ a runtime system registry the host's phase loop dispatches (mirrors the existing EV6 foreign event-listener array). A module registers avoid()system into a phase; it runs each frame after the host's own handlers.extern function … = "sym". Resolved at dylib load.-undefined dynamic_lookupbindsludic_*back to the host image.Plan (layered, each independently testable)
ludic_register_system(fn, phase_id)+ a stable phase-id enum + dynamic dispatch appended to every phase in the frame loop (emit_world/emit_game). Prove in-process.ludicc --emit-module: nomain/world-table/runtime; emit the module's functions,declarethe host reflection ABI it references, and a@llvm.global_ctorsentry calling a conventional init. Build as a dylib with dynamic_lookup.x build-lib(compile a package to a per-target dylib in the content-addressed store + an interface stub ofexternsignatures) and prebuilt consumption (kind prebuiltlinks the artifact for the build target, rpath into the store; hard error when the target is missing).Non-goals
Implemented and shipped to
main(commitse1537d2compiler,075a7b1package manager).A developer can now ship a compiled artifact whose exported functions, systems and components a consumer uses without the source — full binary via the reflection ABI, exactly as decided.
Compiler (
e1537d2):ludic_register_system(fn, phase)+ a registry the frame loop dispatches after each phase's own handlers — the systems analogue of the existingludic_register_prop(dynamic components) and a mirror of the EV6 foreign event-listener array.--emit-module: compiles a package to a position-independent module — nomain, no world table — that declares the host reflection ABI it calls and carries a load-time constructor registering its@System(Phase)functions and runningmodule_init. Built as a dylib with-undefined dynamic_lookup, it bindsludic_*back to the host at load.@System(Phase)annotation marks a module system (the compiler supplies its address — Ludic source cannot take one).-O2, so golden renders stay byte-identical and the C-free bootstrap fixpoint holds.Package manager (
075a7b1):x build-lib <module.ludic>→lib/<target>/lib<name>.dylib(with an@rpathinstall name).kind prebuiltdeps resolve + link like any dependency;x link-flagsprints the clang flags for any build system (x appsplices them in-repo). Missing the build target is a hard error.Proven end-to-end (hermetic, macOS-gated in
x test-pkg): a module exporting a component + an@System(Update)+ a function is built withx build-lib, fetched as a prebuilt dependency, and linked into a consumer game that never saw its source — the module's system mutates the shared world and its function is callable (output3 42). Package suite 17/0; full suite 87/0; fixpoint intact.Boundaries (as designed): native/dylib-only (no wasm/cross-compile), second-class ECS (dynamic by-name components, one indirect call per registered system), not part of the deterministic/replay core. Source packages (#63) remain the portable, first-class path. Docs:
docs/PACKAGES.md.Closing as done.