|
Download docs/ARCHITECTURE.md from geter62/monkeystudio: direct link, hf CLI and curl.
- Browser
- Download file 6.13 kB
-
https://huggingface.co/geter62/monkeystudio/resolve/main/docs/ARCHITECTURE.md
- Command line
-
hf download hf://geter62/monkeystudio/docs/ARCHITECTURE.md
-
curl -L -o ARCHITECTURE.md https://huggingface.co/geter62/monkeystudio/resolve/main/docs/ARCHITECTURE.md
6.13 kB
| # 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. | |