Skip to content

Commit 8f1b410

Browse files
feat: Implement a cache of script inheritence (#314)
* Implement a cache of script inheritence There was a clobbered importance order that is no longer being called cosmetically the Inheritance stacks are now being saved so they don't have to be recalced again on each iteration, this saves the majority of the sorting time * Add Comments Comments describing the sorting process and artifcats * Tiny Style Change * Remove _sort_extensions_from_load_order * Remove a space
1 parent 26bcc8d commit 8f1b410

File tree

3 files changed

+40
-82
lines changed

3 files changed

+40
-82
lines changed

addons/mod_loader/internal/script_extension.gd

Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,35 @@ extends Reference
77

88
const LOG_NAME := "ModLoader:ScriptExtension"
99

10-
11-
# Couple the extension paths with the parent paths and the extension's mod id
12-
# in a ScriptExtensionData resource
10+
# Sort script extensions by inheritance and apply them in order
1311
static func handle_script_extensions() -> void:
14-
var script_extension_data_array := []
12+
var extension_paths := []
1513
for extension_path in ModLoaderStore.script_extensions:
16-
17-
if not File.new().file_exists(extension_path):
14+
if File.new().file_exists(extension_path):
15+
extension_paths.push_back(extension_path)
16+
else:
1817
ModLoaderLog.error("The child script path '%s' does not exist" % [extension_path], LOG_NAME)
19-
continue
20-
21-
var child_script = ResourceLoader.load(extension_path)
22-
23-
var mod_id: String = extension_path.trim_prefix(_ModLoaderPath.get_unpacked_mods_dir_path()).get_slice("/", 0)
24-
25-
var parent_script: Script = child_script.get_base_script()
26-
var parent_script_path: String = parent_script.resource_path
27-
28-
script_extension_data_array.push_back(
29-
ScriptExtensionData.new(extension_path, parent_script_path, mod_id)
30-
)
31-
32-
# Sort the extensions based on dependencies
33-
script_extension_data_array = _sort_extensions_from_load_order(script_extension_data_array)
34-
35-
# Inheritance is more important so this called last
36-
script_extension_data_array.sort_custom(InheritanceSorting, "_check_inheritances")
37-
18+
19+
# Sort by inheritance
20+
extension_paths.sort_custom(InheritanceSorting.new(), "_check_inheritances")
21+
3822
# Load and install all extensions
39-
for extension in script_extension_data_array:
40-
var script: Script = apply_extension(extension.extension_path)
23+
for extension in extension_paths:
24+
var script: Script = apply_extension(extension)
4125
_reload_vanilla_child_classes_for(script)
4226

4327

44-
# Inner class so the sort function can be called by handle_script_extensions()
28+
# Sorts script paths by their ancestors. Scripts are organized by their common
29+
# acnestors then sorted such that scripts extending script A will be before
30+
# a script extending script B if A is an ancestor of B.
4531
class InheritanceSorting:
32+
var stack_cache := {}
4633

47-
static func _check_inheritances(extension_a: ScriptExtensionData, extension_b: ScriptExtensionData) -> bool:
48-
var a_stack := []
49-
var parent_script: Script = load(extension_a.extension_path)
50-
while parent_script:
51-
a_stack.push_front(parent_script.resource_path)
52-
parent_script = parent_script.get_base_script()
53-
a_stack.pop_back()
54-
55-
var b_stack := []
56-
parent_script = load(extension_b.extension_path)
57-
while parent_script:
58-
b_stack.push_front(parent_script.resource_path)
59-
parent_script = parent_script.get_base_script()
60-
b_stack.pop_back()
34+
# Comparator function. return true if a should go before b. This may
35+
# enforce conditions beyond the stated inheritance relationship.
36+
func _check_inheritances(extension_a: String, extension_b: String) -> bool:
37+
var a_stack := cached_inheritances_stack(extension_a)
38+
var b_stack := cached_inheritances_stack(extension_b)
6139

6240
var last_index: int
6341
for index in a_stack.size():
@@ -68,10 +46,28 @@ class InheritanceSorting:
6846
last_index = index
6947

7048
if last_index < b_stack.size():
71-
# 'a' has a shorter stack
7249
return true
7350

74-
return extension_a.extension_path < extension_b.extension_path
51+
return extension_a < extension_b
52+
53+
# Returns a list of scripts representing all the ancestors of the extension
54+
# script with the most recent ancestor last.
55+
#
56+
# Results are stored in a cache keyed by extension path
57+
func cached_inheritances_stack(extension_path: String) -> Array:
58+
if stack_cache.has(extension_path):
59+
return stack_cache[extension_path]
60+
61+
var stack := []
62+
63+
var parent_script: Script = load(extension_path)
64+
while parent_script:
65+
stack.push_front(parent_script.resource_path)
66+
parent_script = parent_script.get_base_script()
67+
stack.pop_back()
68+
69+
stack_cache[extension_path] = stack
70+
return stack
7571

7672

7773
static func apply_extension(extension_path: String) -> Script:
@@ -114,19 +110,6 @@ static func apply_extension(extension_path: String) -> Script:
114110

115111
return child_script
116112

117-
118-
# Sort an array of ScriptExtensionData following the load order
119-
static func _sort_extensions_from_load_order(extensions: Array) -> Array:
120-
var extensions_sorted := []
121-
122-
for _mod_data in ModLoaderStore.mod_load_order:
123-
for script in extensions:
124-
if script.mod_id == _mod_data.dir_name:
125-
extensions_sorted.push_front(script)
126-
127-
return extensions_sorted
128-
129-
130113
# Reload all children classes of the vanilla class we just extended
131114
# Calling reload() the children of an extended class seems to allow them to be extended
132115
# e.g if B is a child class of A, reloading B after apply an extender of A allows extenders of B to properly extend B, taking A's extender(s) into account

addons/mod_loader/mod_loader_setup.gd

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ const new_global_classes := [
2525
"class": "ModManifest",
2626
"language": "GDScript",
2727
"path": "res://addons/mod_loader/resources/mod_manifest.gd"
28-
}, {
29-
"base": "Resource",
30-
"class": "ScriptExtensionData",
31-
"language": "GDScript",
32-
"path": "res://addons/mod_loader/resources/script_extension_data.gd"
3328
}, {
3429
"base": "Resource",
3530
"class": "ModLoaderCurrentOptions",

addons/mod_loader/resources/script_extension_data.gd

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)