Skip to content

Commit

Permalink
feat: Implement a cache of script inheritence (#314)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
boardengineer authored Jul 2, 2023
1 parent 26bcc8d commit 8f1b410
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 82 deletions.
97 changes: 40 additions & 57 deletions addons/mod_loader/internal/script_extension.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,35 @@ extends Reference

const LOG_NAME := "ModLoader:ScriptExtension"


# Couple the extension paths with the parent paths and the extension's mod id
# in a ScriptExtensionData resource
# Sort script extensions by inheritance and apply them in order
static func handle_script_extensions() -> void:
var script_extension_data_array := []
var extension_paths := []
for extension_path in ModLoaderStore.script_extensions:

if not File.new().file_exists(extension_path):
if File.new().file_exists(extension_path):
extension_paths.push_back(extension_path)
else:
ModLoaderLog.error("The child script path '%s' does not exist" % [extension_path], LOG_NAME)
continue

var child_script = ResourceLoader.load(extension_path)

var mod_id: String = extension_path.trim_prefix(_ModLoaderPath.get_unpacked_mods_dir_path()).get_slice("/", 0)

var parent_script: Script = child_script.get_base_script()
var parent_script_path: String = parent_script.resource_path

script_extension_data_array.push_back(
ScriptExtensionData.new(extension_path, parent_script_path, mod_id)
)

# Sort the extensions based on dependencies
script_extension_data_array = _sort_extensions_from_load_order(script_extension_data_array)

# Inheritance is more important so this called last
script_extension_data_array.sort_custom(InheritanceSorting, "_check_inheritances")


# Sort by inheritance
extension_paths.sort_custom(InheritanceSorting.new(), "_check_inheritances")

# Load and install all extensions
for extension in script_extension_data_array:
var script: Script = apply_extension(extension.extension_path)
for extension in extension_paths:
var script: Script = apply_extension(extension)
_reload_vanilla_child_classes_for(script)


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

static func _check_inheritances(extension_a: ScriptExtensionData, extension_b: ScriptExtensionData) -> bool:
var a_stack := []
var parent_script: Script = load(extension_a.extension_path)
while parent_script:
a_stack.push_front(parent_script.resource_path)
parent_script = parent_script.get_base_script()
a_stack.pop_back()

var b_stack := []
parent_script = load(extension_b.extension_path)
while parent_script:
b_stack.push_front(parent_script.resource_path)
parent_script = parent_script.get_base_script()
b_stack.pop_back()
# Comparator function. return true if a should go before b. This may
# enforce conditions beyond the stated inheritance relationship.
func _check_inheritances(extension_a: String, extension_b: String) -> bool:
var a_stack := cached_inheritances_stack(extension_a)
var b_stack := cached_inheritances_stack(extension_b)

var last_index: int
for index in a_stack.size():
Expand All @@ -68,10 +46,28 @@ class InheritanceSorting:
last_index = index

if last_index < b_stack.size():
# 'a' has a shorter stack
return true

return extension_a.extension_path < extension_b.extension_path
return extension_a < extension_b

# Returns a list of scripts representing all the ancestors of the extension
# script with the most recent ancestor last.
#
# Results are stored in a cache keyed by extension path
func cached_inheritances_stack(extension_path: String) -> Array:
if stack_cache.has(extension_path):
return stack_cache[extension_path]

var stack := []

var parent_script: Script = load(extension_path)
while parent_script:
stack.push_front(parent_script.resource_path)
parent_script = parent_script.get_base_script()
stack.pop_back()

stack_cache[extension_path] = stack
return stack


static func apply_extension(extension_path: String) -> Script:
Expand Down Expand Up @@ -114,19 +110,6 @@ static func apply_extension(extension_path: String) -> Script:

return child_script


# Sort an array of ScriptExtensionData following the load order
static func _sort_extensions_from_load_order(extensions: Array) -> Array:
var extensions_sorted := []

for _mod_data in ModLoaderStore.mod_load_order:
for script in extensions:
if script.mod_id == _mod_data.dir_name:
extensions_sorted.push_front(script)

return extensions_sorted


# Reload all children classes of the vanilla class we just extended
# Calling reload() the children of an extended class seems to allow them to be extended
# 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
Expand Down
5 changes: 0 additions & 5 deletions addons/mod_loader/mod_loader_setup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ const new_global_classes := [
"class": "ModManifest",
"language": "GDScript",
"path": "res://addons/mod_loader/resources/mod_manifest.gd"
}, {
"base": "Resource",
"class": "ScriptExtensionData",
"language": "GDScript",
"path": "res://addons/mod_loader/resources/script_extension_data.gd"
}, {
"base": "Resource",
"class": "ModLoaderCurrentOptions",
Expand Down
20 changes: 0 additions & 20 deletions addons/mod_loader/resources/script_extension_data.gd

This file was deleted.

0 comments on commit 8f1b410

Please sign in to comment.