SDOM supports two complementary ways to build apps: directly in C++ and via Lua scripting (powered by Sol2). Lua is first‑class but optional—you can adopt it incrementally or run entirely without it. When enabled, Lua accelerates iteration, enables data‑driven scenes, and allows runtime behavior changes without recompilation.
Goals
- Fast iteration and prototyping for UI, scenes, and behaviors
- Data‑driven creation of resources and display objects
- Runtime stage/scene swapping and dialog scripting
- Test harnesses that exercise systems without rebuilding
Modes of Use
- C++‑only: Construct everything with initializer structs and APIs.
- C++ + Lua (hybrid): Use Lua for data/config and glue; keep heavy logic in C++.
- Lua‑driven: Define scenes and behavior in Lua; C++ provides performant primitives.
Integration Points
- Factory: Types register creator lambdas that accept either a type‑specific initializer struct or a Lua
sol::table
.
- Constructors: Each type offers
Type(const InitType&)
and Type(const sol::table&)
protected/ private constructors; Factory is a friend.
- Lifecycle: Core exposes hooks (init, update, event, render, unit tests) that can call into Lua and vice‑versa.
- Scenes: Swap the active Stage (DOM root) at runtime from C++ or Lua.
Example: Creating with Lua
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.script("box = { name = 'myBox', x = 10, y = 20, width = 200, height = 150 }");
auto box = factory->create("Box", lua["box"]);
Safety and Performance
- Typed handles (DomHandle/ResHandle) decouple references from object lifetimes.
- Factory enforces unique names and validates creation; lookups are type‑safe.
- Lua boundaries are thin: parse once, then operate on native objects; keep hot paths in C++.
Source of Truth
Design docs are living and may evolve. When in doubt, defer to headers/Doxygen and unit tests as the canonical behavior, and open an issue or PR to align docs.
SDOM API considerations for Lua
- Binding conventions:
- Expose minimal, stable surfaces; prefer methods over public fields.
- Use clear namespaces/tables (e.g.,
sdom.core
, sdom.factory
, sdom.events
).
- Map enums to string constants or integer values; document both.
- Handles and identity:
- Pass DomHandle/ResHandle (or names) into Lua; avoid exposing raw pointers.
- Provide
resolve(handle)
/exists(name)
helpers to validate before use.
- Factory usage:
- Accept
sol::table
for creation; validate name
uniqueness and required fields.
- Offer typed convenience creators (e.g.,
factory:create_box(tbl)
) that normalize fields.
- Lifecycle hooks:
- Allow registering Lua callbacks for init, update(dt), on_event(evt), render, unit_tests.
- Ensure callbacks are optional and isolated; guard with try/catch and logging.
- Threading:
- Run Lua on the main thread unless the VM is explicitly made thread‑safe.
- If background work is needed, post results/events back to the main thread.
- Error handling:
- Convert Lua errors to structured engine logs; show file/line and stack trace.
- Fail fast on invalid creations; keep partial state consistent.
- Performance:
- Keep hot paths in C++; avoid per‑frame table allocations inside tight loops.
- Prefer prebound closures or lightuserdata for frequently called callbacks.
- Security/sandboxing:
- Restrict libraries when embedding (only
base
by default); whitelist as needed.
- Avoid exposing file/network primitives unless required; gate behind config flags.
Next steps (Lua integration)
- Bind the Factory and core types to Lua (sol2) with a small, documented surface.
- Add a Lua demo scene that constructs a Stage and a few DisplayObjects.
- Wire lifecycle hooks (
onInit
, onUpdate
, onEvent
, onRender
, onUnitTests
) to optional Lua functions.
- Provide typed creator helpers and validation for common objects (e.g., Box, Label).
- Add unit tests that load Lua scripts to validate creation, events, and anchoring.
Module-style configuration and lifecycle registration (recommended)
SDOM supports module-style Lua configuration files that return a config
table. This keeps globals out of the Lua namespace and is the recommended pattern for portable configs:
local config = {
Core = { windowWidth = 1024, windowHeight = 768, ... }
}
-- Register lifecycle callbacks
Core:registerOn("Init", function() print("OnInit") end)
Core:registerOn("Update", function(dt) end)
return config
Notes:
- The embedded Core Lua state exposes
Core
, CLR
, and unit-test helpers. The Core:registerOn(name, fn)
helper accepts short names (Init, Quit, Update, Event, Render, UnitTest, WindowResize) and binds the provided Lua function as a protected callback.
- Config files may either return the config table (preferred) or set a global
config
(legacy). The host will prefer the returned table and fall back to the global.
- If you rely on
require
for modular callbacks, ensure package.path
is set appropriately (examples set a project-relative path in examples/test/lua/config.lua
).