basic ECS spawner
This commit is contained in:
191
addons/gecs/lib/array_extensions.gd
Normal file
191
addons/gecs/lib/array_extensions.gd
Normal file
@@ -0,0 +1,191 @@
|
||||
class_name ArrayExtensions
|
||||
|
||||
## Intersects two arrays of entities.[br]
|
||||
## In common terms, use this to find items appearing in both arrays.
|
||||
## [param array1] The first array to intersect.[br]
|
||||
## [param array2] The second array to intersect.[br]
|
||||
## [b]return Array[/b] The intersection of the two arrays.
|
||||
static func intersect(array1: Array, array2: Array) -> Array:
|
||||
# Optimize by using the smaller array for lookup
|
||||
if array1.size() > array2.size():
|
||||
return intersect(array2, array1)
|
||||
|
||||
# Use dictionary for O(1) lookup instead of O(n) Array.has()
|
||||
var lookup := {}
|
||||
for entity in array2:
|
||||
lookup[entity] = true
|
||||
|
||||
var result: Array = []
|
||||
for entity in array1:
|
||||
if lookup.has(entity):
|
||||
result.append(entity)
|
||||
return result
|
||||
|
||||
## Unions two arrays of entities.[br]
|
||||
## In common terms, use this to combine items without duplicates.[br]
|
||||
## [param array1] The first array to union.[br]
|
||||
## [param array2] The second array to union.[br]
|
||||
## [b]return Array[/b] The union of the two arrays.
|
||||
static func union(array1: Array, array2: Array) -> Array:
|
||||
# Use dictionary to track uniqueness for O(1) lookups
|
||||
var seen := {}
|
||||
var result: Array = []
|
||||
|
||||
# Add all from array1
|
||||
for entity in array1:
|
||||
if not seen.has(entity):
|
||||
seen[entity] = true
|
||||
result.append(entity)
|
||||
|
||||
# Add unique items from array2
|
||||
for entity in array2:
|
||||
if not seen.has(entity):
|
||||
seen[entity] = true
|
||||
result.append(entity)
|
||||
|
||||
return result
|
||||
|
||||
## Differences two arrays of entities.[br]
|
||||
## In common terms, use this to find items only in the first array.[br]
|
||||
## [param array1] The first array to difference.[br]
|
||||
## [param array2] The second array to difference.[br]
|
||||
## [b]return Array[/b] The difference of the two arrays (entities in array1 not in array2).
|
||||
static func difference(array1: Array, array2: Array) -> Array:
|
||||
# Use dictionary for O(1) lookup instead of O(n) Array.has()
|
||||
var lookup := {}
|
||||
for entity in array2:
|
||||
lookup[entity] = true
|
||||
|
||||
var result: Array = []
|
||||
for entity in array1:
|
||||
if not lookup.has(entity):
|
||||
result.append(entity)
|
||||
return result
|
||||
|
||||
## systems_by_group is a dictionary of system groups and their systems
|
||||
## { "Group1": [SystemA, SystemB], "Group2": [SystemC, SystemD] }
|
||||
static func topological_sort(systems_by_group: Dictionary) -> void:
|
||||
# Iterate over each group key in 'systems_by_group'
|
||||
for group in systems_by_group.keys():
|
||||
var systems = systems_by_group[group]
|
||||
# If the group has 1 or fewer systems, no sorting is needed
|
||||
if systems.size() <= 1:
|
||||
continue
|
||||
|
||||
# Create two data structures:
|
||||
# 1) adjacency: stores, for a given system, which systems must come after it
|
||||
# 2) indegree: tracks how many "prerequisite" systems each system has
|
||||
var adjacency = {}
|
||||
var indegree = {}
|
||||
var wildcard_front = []
|
||||
var wildcard_back = []
|
||||
for s in systems:
|
||||
adjacency[s] = []
|
||||
indegree[s] = 0
|
||||
|
||||
# Build adjacency and indegree counts based on dependencies returned by s.deps()
|
||||
for s in systems:
|
||||
var deps_dict = s.deps()
|
||||
|
||||
# Check for Runs.Before array on s
|
||||
# If present, each item in s.Runs.Before means "s must run before that item"
|
||||
# So we add the item to adjacency[s], and increment the item's indegree
|
||||
# If item is null or ECS.wildcard, we treat it as "run before everything" by pushing 's' onto wildcard_front
|
||||
if deps_dict.has(System.Runs.Before):
|
||||
for b in deps_dict[System.Runs.Before]:
|
||||
if b == null:
|
||||
# ECS.wildcard AKA 'null' means s should run before all systems
|
||||
wildcard_front.append(s)
|
||||
else:
|
||||
# Find system instance that matches the dependency type
|
||||
var target_system = _find_system_by_type(systems, b)
|
||||
if target_system:
|
||||
# Normal dependency within the group
|
||||
adjacency[s].append(target_system)
|
||||
indegree[target_system] += 1
|
||||
|
||||
# Check for Runs.After array on s
|
||||
# If present, each item in s.Runs.After means "s must run after that item"
|
||||
# So we add 's' to adjacency[item], and increment s's indegree
|
||||
# If item is null or ECS.wildcard, we treat it as "run after everything" by pushing 's' onto wildcard_back
|
||||
if deps_dict.has(System.Runs.After):
|
||||
for a in deps_dict[System.Runs.After]:
|
||||
if a == null:
|
||||
# ECS.wildcard AKA 'null' means s should run after all systems
|
||||
wildcard_back.append(s)
|
||||
else:
|
||||
# Find system instance that matches the dependency type
|
||||
var dependency_system = _find_system_by_type(systems, a)
|
||||
if dependency_system:
|
||||
# Normal dependency within the group
|
||||
adjacency[dependency_system].append(s)
|
||||
indegree[s] += 1
|
||||
|
||||
# Kahn's Algorithm begins:
|
||||
# 1) Insert all systems with zero indegree into a queue
|
||||
# 2) Pop from the queue, add to sorted_result
|
||||
# 3) Decrement indegree for each adjacent system
|
||||
# 4) Any adjacent system that reaches zero indegree is appended to the queue
|
||||
var queue = []
|
||||
|
||||
# Adjust for wildcard_front and wildcard_back:
|
||||
# wildcard_front: s runs before everything -> point s -> other
|
||||
for w in wildcard_front:
|
||||
for other in systems:
|
||||
if other != w and not adjacency[w].has(other):
|
||||
adjacency[w].append(other)
|
||||
indegree[other] += 1
|
||||
|
||||
# wildcard_back: s runs after everything -> point other -> s
|
||||
for w in wildcard_back:
|
||||
for other in systems:
|
||||
if other != w and not adjacency[other].has(w):
|
||||
adjacency[other].append(w)
|
||||
indegree[w] += 1
|
||||
|
||||
# Collect all systems with zero indegree into the queue as our starting point
|
||||
for s in systems:
|
||||
if indegree[s] == 0:
|
||||
queue.append(s)
|
||||
|
||||
var sorted_result = []
|
||||
|
||||
# While there are systems with no remaining prerequisites
|
||||
while queue.size() > 0:
|
||||
var current = queue.pop_front()
|
||||
# Add that system to the sorted list
|
||||
sorted_result.append(current)
|
||||
# For each system that depends on 'current'
|
||||
for nxt in adjacency[current]:
|
||||
# Decrement its indegree because 'current' is now accounted for
|
||||
indegree[nxt] -= 1
|
||||
# If it has no more prerequisites, add it to the queue
|
||||
if indegree[nxt] == 0:
|
||||
queue.append(nxt)
|
||||
|
||||
# If we successfully placed all systems, overwrite the original array with sorted_result
|
||||
if sorted_result.size() == systems.size():
|
||||
systems_by_group[group] = sorted_result
|
||||
else:
|
||||
assert(
|
||||
false,
|
||||
(
|
||||
"Topological sort failed for group '%s'. Possible cycle or mismatch in dependencies."
|
||||
% group
|
||||
)
|
||||
)
|
||||
# Otherwise, we found a cycle or mismatch. Fallback to the original unsorted array
|
||||
systems_by_group[group] = systems
|
||||
|
||||
# The function modifies 'systems_by_group' in-place with a topologically sorted order
|
||||
|
||||
## Helper function to find a system instance by its type/class
|
||||
static func _find_system_by_type(systems: Array, target_type) -> System:
|
||||
for system in systems:
|
||||
# Check if the system is an instance of the target type
|
||||
if system.get_script() == target_type:
|
||||
return system
|
||||
# Also check class name matching for backward compatibility
|
||||
if system.get_script() and system.get_script().get_global_name() == str(target_type).get_file().get_basename():
|
||||
return system
|
||||
return null
|
||||
Reference in New Issue
Block a user