Skip to content

Commit

Permalink
removed recursion from hook call sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
pirey0 committed Nov 18, 2024
1 parent bcd48ce commit d9dae4e
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 50 deletions.
58 changes: 13 additions & 45 deletions addons/mod_loader/api/hook.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ extends RefCounted
## If the hooked method is [code]static[/code], it will contain the [GDScript] itself.
var reference_object: Object

# The mod hook callable or vanilla method
var _method: Callable
var _next_hook: ModLoaderHook
var _callbacks := []
var _callback_index :=-1


func _init(method: Callable, next_hook: ModLoaderHook = null) -> void:
_method = method
_next_hook = next_hook
reference_object = _next_hook.reference_object if _next_hook else _method.get_object()

func _init(reference_object:Object, callbacks : Array) -> void:
self.reference_object = reference_object
_callbacks = callbacks
_callback_index = callbacks.size()

## Will execute the next mod hook callable or vanilla method and return the result.[br]
## Make sure to call this method [i]somewhere[/i] in the [param mod_callable] you pass to [method ModLoaderMod.add_hook]. [br]
Expand All @@ -27,43 +25,13 @@ func _init(method: Callable, next_hook: ModLoaderHook = null) -> void:
##
## [br][b]Returns:[/b] [Variant][br][br]
func execute_next(args := []) -> Variant:
if _next_hook:
return _next_hook._execute(args)

return null


# _execute just brings the logic to the current hook
# instead of having it all "external" in the previous hook's execute_next
func _execute(args := []) -> Variant:
# No next hook means we are at the end of the chain and call the vanilla method directly
if not _next_hook:
return _method.callv(args)
_callback_index -= 1
assert(_callback_index >= 0, "_callback_index should never be negative. ModLoaderHook was modified in an unsupported way.")

return _method.callv([self] + args)
var callback = _callbacks[_callback_index]

#Vanilla call is always at index 0 and needs to be called without the hooked being passed
if _callback_index == 0:
return callback.callv(args)

# This starts the chain of hooks, which goes as follows:
# _execute 1
# calls _method, the stored mod_callable
# if that method is a mod hook, it passes the ModLoaderHook object along
# mod_callable 1
# the mod hook is implemented by modders, here they can change parameters
# it needs to call execute_next, otherwise the chain breaks
# execute_next 1
# that then calls _execute on the next hook
# _execute 2
# calls _method
# if _method contains the vanilla method, it is called directly
# otherwise we go another layer deeper
# _method (vanilla) returns
# _execute 2 returns
# execute_next 1 returns
# mod_callable 1
# at this point the final return value can be modded again
# mod_callable 1 returns
# _execute 1 returns the final value
# and _execute_chain spits it back out to _ModLoaderHooks.call_hooks
# which was called from the processed vanilla method
func _execute_chain(args := []) -> Variant:
return _execute(args)
return callback.callv([self] + args)
8 changes: 3 additions & 5 deletions addons/mod_loader/internal/hooks.gd
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ static func call_hooks(vanilla_method: Callable, args: Array, hook_hash: int) ->
return vanilla_method.callv(args)

# Create a hook chain which will recursively call down until the vanilla method is reached
hooks.reverse()
var chain_hook := ModLoaderHook.new(vanilla_method)
for mod_func in hooks:
chain_hook = ModLoaderHook.new(mod_func, chain_hook)

var callbacks = [vanilla_method]
callbacks.append_array(callbacks)
var chain_hook := ModLoaderHook.new(vanilla_method.get_object(), callbacks)
return chain_hook._execute_chain(args)


Expand Down

0 comments on commit d9dae4e

Please sign in to comment.