Skip to content

Commit 143031e

Browse files
committed
Added basic code for a pooled factory.
1 parent e35e244 commit 143031e

File tree

6 files changed

+146
-0
lines changed

6 files changed

+146
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
class_name Pool
2+
extends RefCounted
3+
4+
var _prefab: PackedScene
5+
var _inactive: Array[Node] = []
6+
var _active: Array[Node] = []
7+
var _prewarm: int
8+
var _pool_root: Node # where pooled nodes live when inactive
9+
10+
func _init(prefab: PackedScene, prewarm: int, pool_root: Node) -> void:
11+
_prefab = prefab
12+
_prewarm = max(prewarm, 0)
13+
_pool_root = pool_root
14+
_prewarm_nodes()
15+
16+
17+
func _prewarm_nodes() -> void:
18+
for i in _prewarm:
19+
var n := _instantiate()
20+
_idle(n)
21+
22+
23+
func acquire() -> Node:
24+
if _inactive.is_empty():
25+
return _mark_active(_instantiate())
26+
var n := _inactive.pop_back() as Node
27+
return _mark_active(n)
28+
29+
30+
func recycle(n: Node) -> void:
31+
if not _active.has(n):
32+
return
33+
_active.erase(n)
34+
_idle(n)
35+
36+
37+
func _instantiate() -> Node:
38+
var n := _prefab.instantiate()
39+
return n
40+
41+
42+
func _mark_active(n: Node) -> Node:
43+
# visible/ready to use; parent is set by the factory caller
44+
n.set_process(true)
45+
if n.has_property("visible"):
46+
n.visible = true
47+
_active.append(n)
48+
return n
49+
50+
51+
func _idle(n: Node) -> void:
52+
# move to pool root, disable processing & hide
53+
if n.get_parent():
54+
n.get_parent().remove_child(n)
55+
_pool_root.add_child(n)
56+
n.set_process(false)
57+
if n.has_property("visible"):
58+
n.visible = false
59+
_inactive.append(n)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://bf1f4hwymbj57
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class_name PoolTag
2+
extends Node
3+
# Attached at runtime under each pooled instance
4+
5+
var factory: Node
6+
var id: StringName
7+
8+
func despawn() -> void:
9+
if is_instance_valid(factory):
10+
factory.recycle(id, get_parent())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://c7unljwab3sff
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
extends Node
2+
class_name PooledFactory
3+
4+
# Register your spawnable types here (or expose as exported dictionary).
5+
var _registry: Dictionary = {
6+
"bullet": preload("res://scenes/fireball.tscn"),
7+
"slime": preload("res://scenes/meteor.tscn"),
8+
#"vfx": preload("res://scenes/ultima.tscn"),
9+
}
10+
11+
# Per-ID pool settings (optional; defaults used if missing)
12+
var _settings: Dictionary = {
13+
"bullet": {"prewarm": 64},
14+
"slime": {"prewarm": 8},
15+
"vfx": {"prewarm": 16},
16+
}
17+
18+
var _pools: Dictionary = {} # id -> Pool
19+
var _pool_root: Node # hidden container for inactive nodes
20+
21+
func _ready() -> void:
22+
_pool_root = Node3D.new()
23+
_pool_root.name = "PoolRoot"
24+
_pool_root.visible = false
25+
add_child(_pool_root)
26+
_build_pools()
27+
28+
func _build_pools() -> void:
29+
for id: StringName in _registry.keys():
30+
var prefab: PackedScene = _registry[id]
31+
var prewarm := int(_settings.get(id, {}).get("prewarm", 0))
32+
_pools[id] = Pool.new(prefab, prewarm, _pool_root)
33+
34+
# --- PUBLIC API (pool use is transparent) ---
35+
36+
## Spawn an instance by id. Optionally parent it and pass a config dictionary.
37+
func spawn(id: StringName, parent: Node = null, config := {}) -> Node:
38+
var pool: Pool = _pools.get(id)
39+
if pool == null:
40+
push_error("PooledFactory: unknown id: %s" % id)
41+
return null
42+
43+
var n := pool.acquire()
44+
45+
# Inject a PoolTag (added once)
46+
var tag := n.get_node_or_null("PoolTag") as PoolTag
47+
if tag == null:
48+
tag = PoolTag.new()
49+
tag.name = "PoolTag"
50+
n.add_child(tag)
51+
tag.factory = self
52+
tag.id = id
53+
54+
# Optional product configuration contract
55+
if "configure" in n:
56+
n.configure(config)
57+
58+
# Caller decides parenting; if none provided, keep under factory for convenience
59+
if parent:
60+
parent.add_child(n)
61+
else:
62+
add_child(n)
63+
64+
return n
65+
66+
## Optional: let external scripts explicitly recycle (they don’t have to).
67+
func recycle(id: StringName, node: Node) -> void:
68+
var pool: Pool = _pools.get(id)
69+
if pool == null or node == null:
70+
return
71+
# Allow products to clean up before recycling
72+
if "on_recycled" in node:
73+
node.on_recycled()
74+
pool.recycle(node)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://b2uib63wwbp0v

0 commit comments

Comments
 (0)