diff --git a/docs/docs/reference/core.md b/docs/docs/reference/core.md
index 8240494..01c0f56 100644
--- a/docs/docs/reference/core.md
+++ b/docs/docs/reference/core.md
@@ -36,6 +36,11 @@ Used internally for loading, managing and unloading modules.
Do not modify this.
:::
Stores the path to CORE's installation directory.
+### *Dictionary* cleanup_hooks = *{}*
+:::danger[Don't modify]
+Do not modify this.
+:::
+Contains a list of all registered cleanup hooks.
### *Dictionary* custom_modules = *{}*
### *void* _ready()
:::danger[Don't modify]
@@ -96,6 +101,13 @@ Applies the a configuration.
### *void* cleanup()
Makes sure that CORE does not leak memory on shutdown/unload. \
Unloads all custom modules, built-in modules, frees any of CORE's classes and lastly itself.
+### *int* register_cleanup_hook(*Callable* callable)
+Registers a new cleanup hook. \
+Returns the hook id.
+### *bool* unregister_cleanup_hook_by_id(*int* id)
+Unregisters a cleanup hook by it's id.
+### *bool* unregister_cleanup_hook_by_ref(*Callable* callable)
+Unregisters a cleanup hook by it's reference.
### *bool* is_devmode()
Returns if the framework is in development mode.
### *String* get_format_string(*String* string)
diff --git a/src/core.gd b/src/core.gd
index 227cdd4..8728b9f 100644
--- a/src/core.gd
+++ b/src/core.gd
@@ -57,6 +57,9 @@ var storage: CoreBaseModule
## Stores the path to CORE's installation directory.[br]
## [b]Danger: [i]Don't modify this.[/i][/b]
var basepath: String
+## Contains a list of all registered cleanup hooks.[br]
+## [b]Danger: [i]Don't modify this.[/i][/b]
+var cleanup_hooks: Dictionary = {}
## Contains a list of all loaded custom modules.[br]
## [b]Danger: [i]Don't modify this.[/i][/b]
var custom_modules: Dictionary = {}
@@ -223,24 +226,80 @@ func get_custom_module(module_name: String) -> CoreBaseModule:
return null
return custom_modules[module_name]
-# +++ etc ++
+# +++ cleanup ++
## Makes sure that CORE does not leak memory on shutdown/unload.[br]
-## Unloads all custom modules, built-in modules, frees any of CORE's classes and lastly itself.
+## Unloads all custom modules, built-in modules, frees any of CORE's classes and lastly itself.[br]
+## Only call this function if you're sure that your application or game no longer uses the CORE Framework.
func cleanup() -> void:
loggeri.info("Cleaning up")
+ loggeri.verb("Calling cleanup hooks")
+ for hook in cleanup_hooks:
+ if !cleanup_hooks[hook].is_valid():
+ loggeri.error("Cleanup hook #" + str(hook) + " is invalid")
+ else:
+ loggeri.diag("Calling cleanup hook #" + str(hook))
+ await cleanup_hooks[hook].call()
+ loggeri.verb("Unregistering custom modules")
for module in custom_modules_node.get_children(): await unregister_custom_module(module.name)
+ loggeri.verb("Removing custom module support")
remove_child(custom_modules_node)
custom_modules_node.queue_free()
+ loggeri.verb("Unloading built-in modules")
var modules_reverse: Array[String] = modules.duplicate()
modules_reverse.reverse()
for module in modules_reverse:
await get(module)._cleanup()
- get(module).loggeri.queue_free()
get(module).queue_free()
+ print("Freeing configuration")
config.queue_free()
await get_tree().process_frame
+ print("Freeing")
queue_free()
+# Generates a new cleanup hook id
+func _generate_hook_id() -> int:
+ var id = randi()
+ if cleanup_hooks.has(id):
+ loggeri.warn("New cleanup hook id #" + str(id) + " is already taken")
+ return _generate_hook_id()
+ elif id == -1:
+ loggeri.warn("Invalid cleanup hook id '-1'")
+ return _generate_hook_id()
+ return id
+
+## Registers a new cleanup hook.[br]
+## Returns the hook id.
+func register_cleanup_hook(callable: Callable) -> int:
+ if !callable.is_valid():
+ loggeri.error("Could not add cleanup hook: Callable is not valid")
+ return -1
+
+ var id: int = _generate_hook_id()
+ loggeri.verb("Adding new cleanup hook #" + str(id))
+ cleanup_hooks.merge({ id: callable })
+ return id
+
+## Unregisters a cleanup hook by it's id.
+func unregister_cleanup_hook_by_id(id: int) -> bool:
+ if !cleanup_hooks.has(id):
+ loggeri.error("Could not remove cleanup hook (id): Hook #" + str(id) + " does not exist")
+ return false
+ loggeri.verb("Removed cleanup hook #" + str(id) + " (id)")
+ cleanup_hooks.erase(id)
+ return true
+
+## Unregisters a cleanup hook by it's reference.
+func unregister_cleanup_hook_by_ref(callable: Callable) -> bool:
+ var id: Variant = cleanup_hooks.find_key(callable)
+ if id == null:
+ loggeri.error("Could not remove cleanup hook (ref): Could not find a matching hook")
+ return false
+ if typeof(id) != TYPE_INT: await loggeri.crash("Could not remove cleanup hook (ref): find_key did not return an integer (returned '" + str(id) + "')")
+ loggeri.verb("Removed cleanup hook #" + str(id) + " (ref)")
+ cleanup_hooks.erase(id)
+ return true
+
+# +++ etc +++
## Returns if the framework is in development mode.
func is_devmode() -> bool:
return config.development