hook_chain.gd 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. class_name ModLoaderHookChain
  2. extends RefCounted
  3. ## Small class to keep the state of hook execution chains and move between mod hook calls.[br]
  4. ## For examples, see [method ModLoaderMod.add_hook].
  5. ## The reference object is usually the [Node] that the vanilla script is attached to. [br]
  6. ## If the hooked method is [code]static[/code], it will contain the [GDScript] itself.
  7. var reference_object: Object
  8. var _callbacks: Array[Callable] = []
  9. var _callback_index := -1
  10. const LOG_NAME := "ModLoaderHookChain"
  11. # `callbacks` is kept as untyped Array for simplicity when creating a new chain.
  12. # This approach allows direct use of `[vanilla_method] + hooks` without the need to cast types with Array.assign().
  13. func _init(reference_object: Object, callbacks: Array) -> void:
  14. self.reference_object = reference_object
  15. _callbacks.assign(callbacks)
  16. _callback_index = callbacks.size()
  17. ## Will execute the next mod hook callable or vanilla method and return the result.[br]
  18. ## [br]
  19. ## [br][b]Parameters:[/b][br]
  20. ## - [param args] ([Array]): An array of all arguments passed into the vanilla function. [br]
  21. ## [br]
  22. ## [br][b]Returns:[/b][br]
  23. ## - [Variant]: Return value of the next function in the chain.[br]
  24. ## [br]
  25. ## Make sure to call this method [i][color=orange]once[/color][/i] somewhere in the [param mod_callable] you pass to [method ModLoaderMod.add_hook]. [br]
  26. func execute_next(args := []) -> Variant:
  27. var callback := _get_next_callback()
  28. if not callback:
  29. return
  30. # Vanilla needs to be called without the hook chain being passed
  31. if _is_callback_vanilla():
  32. return callback.callv(args)
  33. return callback.callv([self] + args)
  34. ## Same as [method execute_next], but asynchronous - it can be used if a method uses [code]await[/code]. [br]
  35. ## [br]
  36. ## [br][b]Parameters:[/b][br]
  37. ## - [param args] ([Array]): An array of all arguments passed into the vanilla function. [br]
  38. ## [br]
  39. ## [br][b]Returns:[/b][br]
  40. ## - [Variant]: Return value of the next function in the chain.[br]
  41. ## [br]
  42. ## This hook needs to be used if the vanilla method uses [code]await[/code] somewhere. [br]
  43. ## Make sure to call this method [i][color=orange]once[/color][/i] somewhere in the [param mod_callable] you pass to [method ModLoaderMod.add_hook]. [br]
  44. func execute_next_async(args := []) -> Variant:
  45. var callback := _get_next_callback()
  46. if not callback:
  47. return
  48. # Vanilla needs to be called without the hook chain being passed
  49. if _is_callback_vanilla():
  50. return await callback.callv(args)
  51. return await callback.callv([self] + args)
  52. func _get_next_callback() -> Variant:
  53. _callback_index -= 1
  54. if not _callback_index >= 0:
  55. ModLoaderLog.fatal(
  56. "The hook chain index should never be negative. " +
  57. "A mod hook has called execute_next twice or ModLoaderHookChain was modified in an unsupported way.",
  58. LOG_NAME
  59. )
  60. return
  61. return _callbacks[_callback_index]
  62. func _is_callback_vanilla() -> bool:
  63. return _callback_index == 0