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
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 theFeatureshost 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
ArrayMeshobjects. 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_pooledso 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 singleadd_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
PackedVector3Arrays; 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 PackedArrays (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:
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 newFeatureRegistryentries without touching the core.