Skip to content

feat: script extensions sorter checks load order #357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion addons/mod_loader/api/mod.gd
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const LOG_NAME := "ModLoader:Mod"
# Returns: void
static func install_script_extension(child_script_path: String) -> void:

var mod_id: String = ModLoaderUtils.get_string_in_between(child_script_path, "res://mods-unpacked/", "/")
var mod_id: String = _ModLoaderPath.get_mod_dir(child_script_path)
var mod_data: ModData = get_mod_data(mod_id)
if not ModLoaderStore.saved_extension_paths.has(mod_data.manifest.get_mod_id()):
ModLoaderStore.saved_extension_paths[mod_data.manifest.get_mod_id()] = []
Expand Down
19 changes: 0 additions & 19 deletions addons/mod_loader/internal/mod_loader_utils.gd
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,6 @@ static func _is_valid_global_class_dict(global_class_dict: Dictionary) -> bool:
return true


# Returns the string in between two strings in a provided string
static func get_string_in_between(string: String, initial: String, ending: String) -> String:
var start_index: int = string.find(initial)
if start_index == -1:
ModLoaderLog.error("Initial string not found.", LOG_NAME)
return ""

start_index += initial.length()

var end_index: int = string.find(ending, start_index)
if end_index == -1:
ModLoaderLog.error("Ending string not found.", LOG_NAME)
return ""

var found_string: String = string.substr(start_index, end_index - start_index)

return found_string


# Deprecated
# =============================================================================

Expand Down
21 changes: 21 additions & 0 deletions addons/mod_loader/internal/path.gd
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,24 @@ static func get_path_to_mod_config_file(mod_id: String, config_name: String) ->
var mod_config_dir := get_path_to_mod_configs_dir(mod_id)

return mod_config_dir.plus_file( config_name + ".json")


# Returns the mod directory name ("some-mod") from a given path (e.g. "res://mods-unpacked/some-mod/extensions/extension.gd")
static func get_mod_dir(path: String) -> String:
var initial = ModLoaderStore.UNPACKED_DIR
var ending = "/"
var start_index: int = path.find(initial)
if start_index == -1:
ModLoaderLog.error("Initial string not found.", LOG_NAME)
return ""

start_index += initial.length()

var end_index: int = path.find(ending, start_index)
if end_index == -1:
ModLoaderLog.error("Ending string not found.", LOG_NAME)
return ""

var found_string: String = path.substr(start_index, end_index - start_index)

return found_string
34 changes: 31 additions & 3 deletions addons/mod_loader/internal/script_extension.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extends Reference

const LOG_NAME := "ModLoader:ScriptExtension"


# Sort script extensions by inheritance and apply them in order
static func handle_script_extensions() -> void:
var extension_paths := []
Expand All @@ -30,7 +31,15 @@ static func handle_script_extensions() -> void:
# a script extending script B if A is an ancestor of B.
class InheritanceSorting:
var stack_cache := {}

# This dictionary's keys are mod_ids and it stores the corresponding position in the load_order
var load_order := {}
var unpacked_dir = _ModLoaderPath.get_unpacked_mods_dir_path()


func _init() -> void:
_populate_load_order_table()


# 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:
Expand All @@ -45,10 +54,11 @@ class InheritanceSorting:
return a_stack[index] < b_stack[index]
last_index = index

if last_index < b_stack.size():
if last_index < b_stack.size() - 1:
return true

return extension_a < extension_b
return compare_mods_order(extension_a, extension_b)


# Returns a list of scripts representing all the ancestors of the extension
# script with the most recent ancestor last.
Expand All @@ -68,6 +78,23 @@ class InheritanceSorting:

stack_cache[extension_path] = stack
return stack


# Secondary comparator function for resolving scripts extending the same vanilla script
# Will return whether a comes before b in the load order
func compare_mods_order(extension_a: String, extension_b: String) -> bool:
var mod_a_id: String = _ModLoaderPath.get_mod_dir(extension_a)
var mod_b_id: String = _ModLoaderPath.get_mod_dir(extension_b)

return load_order[mod_a_id] < load_order[mod_b_id]


# Populate a load order dictionary for faster access and comparison between mod ids
func _populate_load_order_table() -> void:
var mod_index := 0
for mod in ModLoaderStore.mod_load_order:
load_order[mod.dir_name] = mod_index
mod_index += 1


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

return child_script


# 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