File size: 6,126 Bytes
84e8204 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | # MonkeyStudio Architecture
## 1. The core problem: 100+ features on 4 GB of RAM
A phone with 4 GB total RAM gives an app roughly **1.2-1.5 GB** before the OS
starts killing it. Blender-style tools (sculpt grids, cloth solvers, hair
particles, rigging) are memory-hungry. The only way to offer many features
without OOM is to **never have them all resident at once**.
MonkeyStudio's answer is a lazy-load + reference-count + LRU-evict memory
layer, plus a fixed pool for GPU objects. Below is how each piece works.
## 2. Memory layer (`scripts/core/`)
### 2.1 `MemoryManager` (autoload)
- Owns a hard **budget** (`asset_budget_mb`, default 512 MB, auto-capped to
1/8 of physical RAM).
- Assets are registered by **id -> path + estimated cost**, *not* loaded.
- `acquire(id)` loads on first use and increments a reference count.
- `release(id)` drops a reference. At zero references the asset becomes an
**eviction candidate**.
- `_ensure_room(need)` evicts **least-recently-used, zero-reference** assets
until the new asset fits. Assets with live references are never evicted, so
eviction can never yank something out from under a running feature.
- Every second it polls `OS.get_memory_info()`; when the OS says < 128 MB is
available it force-evicts to buy headroom.
### 2.2 `FeatureRegistry` (autoload)
- A **catalog** of every tool. Each entry is just metadata
(`name`, `module_path`, `cost_mb`).
- `activate(id)` asks MemoryManager for the module script, instantiates it,
and adds it to the `Features` host node.
- `deactivate(id)` destroys the module node and releases its memory. This is
what makes "close the tool, free the RAM" automatic.
### 2.3 `MeshPool` (autoload)
- Recycles `ArrayMesh` objects. A released mesh has its surfaces cleared
(freeing GPU buffers) but the object is kept for reuse, avoiding per-frame
allocation spikes and fragmentation.
- Bounded by `max_pooled` so the pool itself can't leak.
### 2.4 `VertexBufferManager` (autoload)
- `estimate_cost_mb(vertex_count)` predicts RAM+VRAM before a mesh is built so
MemoryManager can evict first.
- `build_surface()` / `commit()` do a **single** `add_surface_from_arrays()`
upload. Mobile GPUs hate thousands of tiny buffer updates; sculpting strokes
accumulate on the CPU and flush once.
- `recompute_normals()` restores correct lighting after edits.
### 2.5 Adaptive quality
`StudioBus` emits `fps_throttled` when FPS stays below 30. `StudioMain` reacts
by disabling shadows, then the simulation substep budget drops (see below).
This keeps the frame budget bounded on weak GPUs.
## 3. Feature modules (`scripts/modules/`)
Modules implement `_activate(host)` / `_deactivate()` and receive the scene
through the `StudioBus` signal hub (low coupling, safe lazy unload).
| Module | Features | Notes |
|---|---|---|
| `mesh_gen.gd` | 1-20 | Procedural cube/sphere/cylinder/plane/torus/ico + a code-generated monkey head |
| `sculpt.gd` | 21-40 | draw / grab / pinch / smooth / flatten, batched GPU flush |
| `rigging.gd` | 41-60 | Skeleton3D builder + inverse-distance auto-weighting + `Skin` bind |
| `simulation.gd` | 61-80 | Verlet mass-spring cloth & hair with wind + pinned constraints |
| `timeline.gd` | 81-100 | Engine-native `Animation` + `AnimationPlayer` keyframing |
### 3.1 Mesh generation
Pure functions returning `{verts, indices, uvs}`. No GPU allocation until
`spawn()` commits. Costs are predictable, so a 5000-vertex sphere is refused
before it is built if it would blow the budget.
### 3.2 Sculpting
A stroke is `begin -> apply* -> end`. All `apply` calls mutate CPU
`PackedVector3Array`s; `end` performs one upload. Brushes use a smoothstep
falloff. `raycast_screen()` prefers a physics ray against the mesh collider and
falls back to a bounding-sphere test when the mesh has no collider yet.
### 3.3 Rigging
`generate_default_armature()` builds a spine + limbs from the mesh bounds.
`bind_weights()` ranks bones by distance, keeps the nearest 4, normalizes, and
writes `ARRAY_BONES` + `ARRAY_WEIGHTS` (the standard 4-influence mobile
skinning path) plus a `Skin` resource. Result is a normal engine skinned mesh.
### 3.4 Cloth & hair
Fixed-timestep (`1/30 s`) Verlet integration with Jacobi constraint relaxation.
Sub-steps are capped at 4 and backlog is dropped, so a slow frame can never
spiral into a death march. Particles and springs live in `PackedArray`s (no
per-frame allocation). Cloth uploads a triangle grid; hair uploads ribbons.
### 3.5 Timeline
Wraps a real `Animation` resource and `AnimationPlayer`, so interpolation,
looping and playback are engine-native and cached. The module exposes
`play/pause/scrub/add_keyframe/add_bone_keyframe` for a touch UI.
## 4. UI (`scripts/studio/studio_ui.gd`)
Material-ish dark theme built in code (resolution-independent, scaled by
`UI_SCALE`). Layout: top bar (FPS + memory HUD), left tool rail, right
collapsible panels (Tools / Layers / Timeline), bottom playback + scrubber.
Touch targets are >= 48 px.
## 5. Camera (`scripts/studio/touch_camera.gd`)
- 1 finger: orbit (or paint when a sculpt brush is active)
- 2 fingers: pinch = dolly zoom, drag = pan
- 3 fingers: pan
- All targets are exponentially smoothed so jittery touch sampling still feels
fluid. No allocation in the input hot path.
## 6. Testing
`tests/self_test.tscn` runs headless and verifies every subsystem:
```bash
godot --headless res://tests/self_test.tscn
```
It checks mesh generation, all sculpt brushes (vertices actually move), the
skinning channels, cloth falling under gravity, hair creation, and timeline
keyframing. Exit code 0 = pass.
## 7. What is production-ready vs. roadmap
Honest status:
- Memory layer, mesh generation, sculpting, rigging, simulation, timeline and
UI shell are **implemented and tested** as described above.
- Production polish still to do: adaptivity grid (`DynamicLOD`), UV unwrap,
boolean ops, baking, USD/OBJ/glTF import-export, undo stack, a broader brush
set, and device-specific tuning. The architecture is designed so these plug
in as new `FeatureRegistry` entries without touching the core.
|