Current Guarantees
Scene Lock
Each viewport owns a non-recursive mutex covering its scene state (mesh pool, materials, lights, selection, undo stack). The render path acquires the lock for the duration of a frame. Application code that mutates from a non-render thread must wrap the mutation in mop_viewport_scene_lock / mop_viewport_scene_unlock.
mop_viewport_scene_lock(vp);
mop_mesh_set_position(mesh, new_pos);
mop_mesh_set_material(mesh, &mat);
mop_viewport_scene_unlock(vp);
Rules:
- The lock is non-recursive. Do not call
scene_locktwice on the same thread. mop_viewport_renderacquires the lock internally. Do not call render while the application already holds the lock.- Per-mesh setters (
mop_mesh_set_position,_rotation,_scale,_material, etc.) take the lock internally using the mesh's back-pointer to its viewport, so they are safe to call from any thread at any time. - Read-only queries (
mop_viewport_mesh_at,mop_mesh_get_position, snapshot iteration) are safe without the lock only if the application can guarantee no other thread is mutating.
See examples/showcase.c for a worker thread animating the scene concurrent with the main render thread.
No Global Mutable State
The engine has zero global variables. All state is encapsulated in MopViewport and its owned resources. This means:
- Multiple viewports can exist simultaneously
- Each viewport can be operated from a different thread (one thread per viewport)
- No mutex contention between viewports
Backend State Isolation
Each backend creates its own MopRhiDevice. There is no shared state between device instances. For the CPU backend, this is trivially true. For GPU backends, each device may share the underlying driver — but the engine does not coordinate access.
Thread Safety Summary
| Operation | Thread-safe? |
|---|---|
| Create two viewports on two threads | Yes |
| Render two viewports concurrently | Yes |
| Render and pick on the same viewport concurrently | No |
| Access same viewport from two threads without the scene lock | No |
Mutate from a worker thread wrapped in scene_lock / scene_unlock | Yes |
Per-mesh setter (mop_mesh_set_*) from any thread | Yes (internal lock) |
| Snapshot iteration without the lock while another thread mutates | No |
Future: Multi-Threaded Command Recording
The architecture is designed to support future multi-threaded command recording:
-
The RHI
drawfunction takes all data by value (theMopRhiDrawCallstruct). No shared mutable state is referenced. -
The viewport mesh array is read-only during rendering — transforms are set before
mop_viewport_render, not during. -
A future
MopRhiCommandListabstraction could allow recording draw calls on multiple threads, with a single-threaded submit phase.
The current architecture does not implement this — but it does not preclude it. No architectural changes are needed to add command lists.