Way of Working¶
This guide captures the development conventions used in ORCA and explains why each step in the component workflow matters. It is the companion to the technical reference in Object + Component System.
Specialist Areas¶
ORCA is structured like a team of specialists, each owning a distinct layer. The WPF analogy: just as WPF had separate framework architects, rendering engineers, controls engineers, scripting engineers, and test engineers, ORCA's codebase maps to the same separation of concerns.
| Specialist role | Files | Key knowledge |
|---|---|---|
| Core / object system | source/core/, include/orca.h |
Component lifecycle, HANDLER macro, message dispatch, FNV1a hashing, struct Object layout |
| Renderer | source/renderer/ |
OpenGL ES, GLSL, texture/mesh BOX_PTR boxing, ViewDef/ViewEntity, palette textures |
| Geometry / math | source/geometry/ |
vec2, vec3, mat4, quaternion, box3 in C |
| UIKit plugin | plugins/UIKit/ |
Layout (MeasureOverride/ArrangeOverride), Node2D/Node3D, button/label/grid components |
| SceneKit / SpriteKit | plugins/SceneKit/, plugins/SpriteKit/ |
3D scene graph, cameras, lights, 2D sprite animation |
| Lua scripting bridge | source/core/object/object_lua_*.c, share/orca/ |
Lua C API, property bindings, send/fetch, behaviour.lua |
| Code generation | tools/, *.xml module files |
PHP (pyphp), XML parsing, codegen templates, make -C tools |
| Build system | Makefile, orca.xcodeproj/ |
GNU Make, pkg-config, Linux/macOS cross-platform C, Xcode project.pbxproj |
| Test harness | tests/, source/orca.c |
Lua test patterns, assert, xvfb-run, make test-headless |
| Documentation | docs/, mkdocs.yml |
Markdown, MkDocs Material, module XML → API doc pipeline |
Quick navigation by intent¶
| "I want to…" | Start here |
|---|---|
| Add a new component | Component workflow below |
| Understand message routing | Object + Component System → Message Dispatch |
| Add a UIKit widget | plugins/UIKit/UIKit.xml + plugins/UIKit/Button.c as a reference |
| Add a property formula/binding | source/core/property/ + Module XML Guide |
| Change what Lua exposes | Edit the module XML, run cd tools && make |
| Debug a rendering issue | source/renderer/r_main.c, r_draw.c; check BOX_IS_PTR before dereferencing |
| Add or fix a test | tests/ — test_styles_lua.lua and test_state_manager.lua are good templates |
| Build the project locally | git submodule update --init --recursive && pip install -e tools/pyphp && make |
The component workflow¶
When you add any component to ORCA — whether refactoring an existing system or building something new — follow this order without exception:
1. XML declaration → 2. Code generation → 3. C handlers
→ 4. Registration → 5. Xcode project → 6. Tests
Each step depends on the previous one. The steps are described in detail in Object + Component System → How to Add a New Component Type.
Why this order is mandatory¶
Step 1: XML is the single source of truth¶
The .xml file drives everything else. It declares:
- The struct that the component's data is stored in
- The FNV1a property hash IDs used in switch statements
- The Proc switch — the dispatch table that routes messages to C handlers
- The Lua bindings
- The REGISTER_CLASS macro that registers the class with the engine
If you skip or delay the XML step, you are writing C code that references symbols that don't exist yet (struct MyComponent, ID_MyComponent_Foo, pMyComponent->field). The code will not compile, and there is no partial-working state to test against.
Step 2: Verify codegen output¶
After running cd tools && make, open <module>_export.c and check that:
1. struct MyComponent { ... } has the expected fields
2. MyComponentProc has a case entry for every <handle message="..."/> you declared
3. REGISTER_CLASS(MyComponent, ...) appears at the bottom
An empty switch {} in the Proc is the single most common sign that you forgot a <handle> element in XML. The component will attach and appear to work, but will silently ignore every message.
Step 3: C handlers must match the XML exactly¶
A HANDLER(MyComponent, NS, Event) function in C corresponds to a <handle message="NS.Event"/> in XML. The code generator forward-declares the function and adds it to the switch. If the XML declaration is missing, the handler is an unreachable function with no caller.
Step 4: Registration at module init¶
OBJ_RegisterClass must be called exactly once per class, at module startup. If the call is missing, obj:addComponent("MyComponent") will succeed silently (it returns without attaching anything useful) — another class of invisible failure.
Step 5: Xcode project¶
The GNU Makefile and the Xcode project are independent build systems. A .c file added to a source/*/components/ directory is picked up by Makefile automatically but is invisible to Xcode until manually added to project.pbxproj. This means the GNU build may succeed while the macOS/Xcode build silently excludes the file and gets a link error.
Step 6: Tests are not optional¶
A test file is the only way to catch the "component attaches but does nothing" failure mode that results from any missed step above. A minimal test should:
- Attach the component to an object
- Set and get each property
- Trigger at least one message and assert a side-effect
Look at tests/test_state_manager.lua and tests/test_styles_lua.lua as templates.
Use case walkthroughs¶
"I want to add a new component to core"¶
- Open
source/core/core.xml - Add a
<class name="MyComponent">with<handles>,<properties>, and optionally<messages> - Run
cd tools && make— inspectsource/core/core_export.cto confirm the struct and Proc switch look correct - Create
source/core/components/MyComponent.cwithHANDLER(...)implementations - Confirm
on_core_registeredincore_export.ccallsOBJ_RegisterClass(&_MyComponent)(or add the call manually if the callback is hand-written) - Add
source/core/components/MyComponent.ctoorca.xcodeproj/project.pbxprojin all four required sections - Create
tests/test_mycomponent.lua - Run
make unite && xvfb-run make test
"I want to add a component to a plugin (e.g. UIKit)"¶
Same as above, but replace source/core/ with plugins/UIKit/ (or the relevant plugin directory) and core.xml with UIKit.xml.
"I want to migrate a legacy embedded Object field into a component"¶
This is the hardest use case. In addition to the standard steps above:
- Do not break the existing API — keep any public
OBJ_GetXxx/OBJ_SetXxxfunctions working during the transition by forwarding to the component - Remove the field from
struct Objectonly after the component is fully functional and tested - Remove the
kCompXxxenum entry and the_GetXxx(obj)accessor macro fromcore_local.h - Remove the manual release call from
OBJ_Release - Delete the old
.cfile (e.g.stateman.c,alias.c) once the component handles all the same behaviour
Do not leave both the old global implementation and the new component active at the same time — the dual-path state is invisible from the outside and leads to subtle bugs.
Dependency rules¶
| Direction | Allowed? |
|---|---|
| Plugin → core | ✅ Yes |
| Core → plugin | ❌ No |
| Plugin A → plugin B | ⚠️ Avoid — use messages |
source/core/ must never #include a header from plugins/. This rule exists because the core is meant to be buildable and testable without any plugins loaded. A core file that includes <plugins/UIKit/UIKit.h> creates a hard compile-time dependency on UIKit, breaks cross-platform builds that exclude UIKit, and makes the engine impossible to use without all plugins present.
If core code needs behaviour from a plugin (e.g. looking up a struct Node field), the correct approach is:
- Define a message (e.g. Object.FindResourceByKey) and have the plugin handle it
- Register a callback or accessor function pointer at startup via a core API
- Pass the data through a generic property lookup instead of a typed struct field
Checklist for every new component MR¶
Use this as a pull request self-review checklist:
[ ] <class> declared in module XML with <handles>, <properties>, <messages>
[ ] cd tools && make run — no errors
[ ] Generated *_export.c has non-empty MyComponentProc switch
[ ] HANDLER(...) implementations in source/*/components/MyComponent.c
[ ] OBJ_RegisterClass(&_MyComponent) called at module init
[ ] MyComponent.c added to orca.xcodeproj/project.pbxproj (4 places)
[ ] tests/test_mycomponent.lua covers attach + properties + at least one message
[ ] make unite passes (C-only build)
[ ] xvfb-run make test passes (full test suite)
[ ] No #include <plugins/...> from source/core/
[ ] If migrating: old code path deleted, not running alongside the new component