File size: 4,906 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
extends Node
##
## FeatureRegistry - catalog of every tool/feature and its RAM budget.
##
## This is what makes "100+ features that never OOM" possible: features are
## *descriptions*, not live objects. A feature is only instantiated when the
## user actually opens it, and torn down when they close it. The registry maps
## each feature id -> {name, module, cost_mb, loaded}.
##
## Modules are GDScript files under res://scripts/modules/. Each module is a
## Node that implements `_activate(host)` and `_deactivate()`.
##

signal feature_activated(id: String)
signal feature_deactivated(id: String)
signal feature_failed(id: String, reason: String)

## id -> { name, module_path, cost_mb, node, active }
var _features: Dictionary = {}
## Currently instantiated feature nodes, keyed by id.
var _live: Dictionary = {}
## Parent node that hosts active feature modules.
var host: Node = null


func _ready() -> void:
	register_all()


## --- Public API -------------------------------------------------------------

## Instantiate and activate a feature. Safe to call twice (no-op if active).
func activate(id: String) -> bool:
	if not _features.has(id):
		feature_failed.emit(id, "unknown feature")
		return false
	if _live.has(id):
		feature_activated.emit(id)
		return true
	var f: Dictionary = _features[id]
	# Reserve memory *before* instantiating so eviction can happen first.
	MemoryManager.register(id, f["module_path"], float(f["cost_mb"]))
	var script: Resource = MemoryManager.acquire(id)
	if script == null or host == null:
		feature_failed.emit(id, "load failed or no host")
		MemoryManager.release(id)
		return false
	var node: Node = (script as GDScript).new()
	node.name = "Feature_" + id
	host.add_child(node)
	if node.has_method("_activate"):
		node.call("_activate", host)
	_live[id] = node
	feature_activated.emit(id)
	return true


## Deactivate and destroy a feature, returning its RAM to the pool.
func deactivate(id: String) -> void:
	if not _live.has(id):
		return
	var node: Node = _live[id]
	if node.has_method("_deactivate"):
		node.call("_deactivate")
	node.queue_free()
	_live.erase(id)
	MemoryManager.release(id)
	# If nothing else references the module, drop the loaded script now.
	MemoryManager.purge(id)
	feature_deactivated.emit(id)


func is_active(id: String) -> bool:
	return _live.has(id)


func list_features() -> Array:
	var ids := _features.keys()
	ids.sort()
	return ids


func feature_count() -> int:
	return _features.size()


func get_info(id: String) -> Dictionary:
	return _features.get(id, {})


## --- Registration -----------------------------------------------------------

func _reg(id: String, name: String, module_path: String, cost_mb: float) -> void:
	_features[id] = {
		"name": name,
		"module_path": module_path,
		"cost_mb": cost_mb,
		"active": false,
	}


## The full feature catalog. Costs are conservative RAM estimates in MB and are
## the numbers MemoryManager uses for eviction decisions.
func register_all() -> void:
	# --- Mesh generation (Features 1-20) ---
	_reg("mesh.cube",    "Cube Primitive",     "res://scripts/modules/mesh_gen.gd", 0.5)
	_reg("mesh.sphere",  "UV Sphere",          "res://scripts/modules/mesh_gen.gd", 1.0)
	_reg("mesh.cylinder","Cylinder",           "res://scripts/modules/mesh_gen.gd", 0.5)
	_reg("mesh.plane",   "Plane",              "res://scripts/modules/mesh_gen.gd", 0.2)
	_reg("mesh.suzanne", "Monkey Head",        "res://scripts/modules/mesh_gen.gd", 2.0)
	_reg("mesh.torus",   "Torus",              "res://scripts/modules/mesh_gen.gd", 1.5)
	_reg("mesh.ico",     "Icosphere",          "res://scripts/modules/mesh_gen.gd", 1.0)
	_reg("mesh.text",    "Text Mesh",          "res://scripts/modules/mesh_gen.gd", 1.0)

	# --- Sculpting (Features 21-40) ---
	_reg("sculpt.draw",    "Draw Brush",    "res://scripts/modules/sculpt.gd", 4.0)
	_reg("sculpt.grab",    "Grab Brush",    "res://scripts/modules/sculpt.gd", 4.0)
	_reg("sculpt.pinch",   "Pinch Brush",   "res://scripts/modules/sculpt.gd", 4.0)
	_reg("sculpt.smooth",  "Smooth Brush",  "res://scripts/modules/sculpt.gd", 4.0)
	_reg("sculpt.flatten", "Flatten Brush", "res://scripts/modules/sculpt.gd", 4.0)

	# --- Rigging (Features 41-60) ---
	_reg("rig.skeleton", "Armature Editor", "res://scripts/modules/rigging.gd", 8.0)
	_reg("rig.weight",   "Weight Paint",    "res://scripts/modules/rigging.gd", 6.0)

	# --- Simulation (Features 61-80) ---
	_reg("sim.cloth", "Cloth Sim",  "res://scripts/modules/simulation.gd", 12.0)
	_reg("sim.hair",  "Hair Sim",   "res://scripts/modules/simulation.gd", 10.0)
	_reg("sim.wind",  "Wind Field", "res://scripts/modules/simulation.gd", 2.0)

	# --- Animation (Features 81-100) ---
	_reg("anim.timeline", "Timeline",     "res://scripts/modules/timeline.gd", 3.0)
	_reg("anim.keyframe", "Keyframe Tool","res://scripts/modules/timeline.gd", 3.0)

	print("[FeatureRegistry] %d features registered" % _features.size())