How it fits together
Three layers, and one rule that keeps them apart.
your game Dart · Flutter widgets, game logic, interface │ ├── orblit_core C++ · archetype entity-component store, over a C ABI │ Dart sees component data as views, not copies │ └── orblit_filament C++ · Filament, into a texture Flutter composites. On Apple platforms, an IOSurface-backed pixel buffer adopted without a readbackThe rule: the core takes no Flutter dependency
Section titled “The rule: the core takes no Flutter dependency”orblit_core, and everything it exposes, knows nothing about Flutter.
That is not tidiness. It is what keeps a second front end possible. Dart on
Flutter is one way to reach the core, TypeScript on QuickJS is another, and a
native front end on SDL3 would be a third. Part of that one exists:
orblit_filament’s native/host drives the renderer from C on SDL3, with no
Dart anywhere, though it does not reach the core yet. If the core depended on
Flutter, each of those would have to be a second engine instead of a second
front end, and the three would drift apart inside a year.
Here is the practical version of the rule, if you are writing engine code. A
package that might be used by a game which is not a Flutter application must
not import package:flutter. Simulation, geometry, rigging, noise, agents,
collision, sequencing and networking all stick to it. That is why they run on
plain Dart and can be tested on a Linux CI runner with no display attached.
Why component data is views and not copies
Section titled “Why component data is views and not copies”The core stores entities by archetype: everything with the same set of components sits together, so a system that wants every transform gets a contiguous run of them.
If Dart received copies, every frame would pay to marshal that run across the ABI and marshal the results back again. Instead, Dart receives a view: typed data backed by the store’s own memory. Reading a column is a read. Writing to one writes to the store.
That bargain has a real cost. A view is only valid while the store’s layout is unchanged. Adding or removing a component can move an archetype’s storage, and a view held across that is a view of the wrong thing. The engine’s rule is that views never outlive the system that asked for them.
Why the renderer is a texture and not a platform view
Section titled “Why the renderer is a texture and not a platform view”A platform view puts native content in a window of its own, positioned over the Flutter surface. It works, but it also means the 3D content is not really in your application’s layout. It cannot be clipped by a rounded rectangle, it cannot be overlapped by a panel, and it does not animate with the rest of the frame.
On macOS and iOS, Orblit renders into a CVPixelBuffer backed by an
IOSurface and hands that to Flutter’s texture registry, which composites it
like any other texture. There is no readback and no copy through the CPU. The
GPU wrote it, and the GPU reads it. The other platforms reach the compositor
in their own ways, two of them with a copy per frame, and platform
support lists them.
Here is what that buys you. The viewport clips, scrolls, sits under other widgets, resizes with a slider, and takes part in a hero animation. In the editor, it is why four viewports and a game view can share one window.
Where the repositories are
Section titled “Where the repositories are”Orblit is several repositories because the parts have genuinely different audiences, not because a monorepo was ruled out. Every game needs the engine, only a multiplayer game needs the networking, and only someone writing scripts needs QuickJS and a TypeScript toolchain.
| Repository | What it is |
|---|---|
orblit |
The engine. Most packages live here. |
orblit-editor |
The editor application |
orblit-examples |
The gallery, the viewport, worked examples |
orblit-net |
Multiplayer |
orblit-script |
TypeScript scripting on QuickJS |
orblit-filament |
The Filament fork the web build compiles from |
Within orblit, the unit is the package, not the repository. pub can
resolve a subdirectory of a git repository, so a package in a monorepo is
already usable on its own. Splitting one repository per package buys nothing,
and it costs you a co-ordinated release every time two of them change
together.
