diff --git a/Test.gd b/Test.gd
index 2c0cdd2..dfbe9ba 100644
--- a/Test.gd
+++ b/Test.gd
@@ -24,13 +24,12 @@ extends Node
var core: Core
var config: CoreConfiguration = CoreConfiguration.new()
-@onready var logger: CoreLoggerInstance = core.logger.get_instance("Test.gd")
+@onready var logger: CoreLoggerInstance = core.logger.get_instance("Test.gd", self)
func _init() -> void:
# Update config keys
config.headless = false
config.development = true
- config.logger_level = CoreTypes.LoggerLevel.DIAG
# Initialize CORE with custom config
core = Core.new(config)
diff --git a/docs/docs/reference/core.md b/docs/docs/reference/core.md
index d3257b8..77ffb4e 100644
--- a/docs/docs/reference/core.md
+++ b/docs/docs/reference/core.md
@@ -19,6 +19,7 @@ CORE's version type number. Resets on every new version and version type.
## Modules
Use these to access CORE's modules.
- [`config`](/reference/coreconfiguration) (**NEVER access this yourself. To change the configuration, use `reload_configuration()` instead**)
+- `scheduler` (runs clean up tasks periodically)
- [`logger`](/reference/logger)
- [`misc`](/reference/misc)
- `logui` (not important for developers, displays the log graphically)
@@ -65,6 +66,11 @@ Initializes all built-in modules during the preinitialization phase.
Do not call this.
:::
Injects CORE's built-in modules into the SceneTree.
+### *void* initialize_scheduler()
+:::danger[Don't call]
+Do not call this.
+:::
+Initializes the framework scheduler.
### *void* complete_init(*bool* no_success_message = *false*)
Waits for all built-in and custom modules to fully initialize. \
\
diff --git a/docs/docs/reference/corebasemodule.md b/docs/docs/reference/corebasemodule.md
index fed7f90..5dec4a0 100644
--- a/docs/docs/reference/corebasemodule.md
+++ b/docs/docs/reference/corebasemodule.md
@@ -26,6 +26,9 @@ It's **strongly** recommended to initialize your module here or stuff might brea
Called when CORE is about to cleanup. \
Use this function to remove any children from the SceneTree and free any nodes. \
If not done you might cause a memory leak.
+### *void* _schedule()
+Called periodically by CORE's scheduler. \
+Use this function to clean up any temporary things.
### *void* _pull_config()
Called after CORE's configuration got updated. \
Probably useless to your module, unless you want to modify another module's settings.
diff --git a/docs/docs/reference/logger.md b/docs/docs/reference/logger.md
index 50173fa..7b4849a 100644
--- a/docs/docs/reference/logger.md
+++ b/docs/docs/reference/logger.md
@@ -15,6 +15,14 @@ Emitted on any log call, permitted or not. \
**origin** contains the keys `source`, `source_clean`, `function` and `line`. \
**format** is set to `""` when **allowed** is set `false`.
+## Variables
+### *Array[CoreLoggerInstance]* instances = *[]*
+:::danger[Don't call]
+Do not call this.
+:::
+Keeps track of all logger instances.
+Unused instances will be cleaned periodically.
+
## Functions
### *void* _log(*CoreTypes.LoggerLevel* level, *String* origin, *String* message)
:::danger[Don't call]
@@ -41,5 +49,5 @@ Don't set `framework_crash` to `true`, thanks!
Handles crashes. Will terminate your game/application immediately.
### *bool* is_level_allowed(*CoreTypes.LoggerLevel* level)
Checks if the specified log level is allowed by the current configuration.
-### *CoreLoggerInstance* get_instance(*String* origin)
+### *CoreLoggerInstance* get_instance(*String* origin, *Object* parent)
Returns a [CoreLoggerInstance](/reference/loggerinstance), which is a fancy word meaning you don't need to pass `origin` every time you want to log something.
diff --git a/docs/docs/reference/loggerinstance.md b/docs/docs/reference/loggerinstance.md
index 7a2d860..8759802 100644
--- a/docs/docs/reference/loggerinstance.md
+++ b/docs/docs/reference/loggerinstance.md
@@ -16,10 +16,15 @@ The origin argument.
:::danger[Don't modify]
Do not modify this.
:::
-For internal purposes only.
+Indicates that the parent class is part of the CORE Framework.
+### *Object* parent
+:::danger[Don't modify]
+Do not modify this.
+:::
+Is set to the parent owning the logger instance.
## Functions
-### *void* _init(*CoreBaseModule* logger_new, *String* origin_new)
+### *void* _init(*CoreBaseModule* logger_new, *String* origin_new, *Object* parent)
The instance constructor.
### *void* diag(*String* message)
Prints a diagnostic message.
diff --git a/src/classes/basemodule.gd b/src/classes/basemodule.gd
index 65b865e..4bf52fe 100644
--- a/src/classes/basemodule.gd
+++ b/src/classes/basemodule.gd
@@ -38,6 +38,9 @@ func _initialize() -> void: initialized = true
## Use this function to remove any children from the SceneTree and free any nodes.[br]
## If not done you might cause a memory leak.
func _cleanup() -> void: pass
+## Called periodically by CORE's scheduler.[br]
+## Use this function to clean up any temporary things.
+func _schedule() -> void: pass
## Called after CORE's configuration got updated.[br]
## Probably useless to your module, unless you want to modify another module's settings.
func _pull_config() -> void: pass
diff --git a/src/classes/loggerinstance.gd b/src/classes/loggerinstance.gd
index 6bce27e..8717c34 100644
--- a/src/classes/loggerinstance.gd
+++ b/src/classes/loggerinstance.gd
@@ -28,14 +28,18 @@ class_name CoreLoggerInstance
var logger: CoreBaseModule
## The origin argument.
var origin: String
-## For internal purposes only.
+## Indicates that the parent class is part of the CORE Framework.
## [b]Note: [i]Don't modify.[/i][/b]
var framework: bool = false
+## Is set to the parent owning the logger instance.
+## [b]Note: [i]Don't modify.[/i][/b]
+var parent: Object
## The instance constructor.
-func _init(logger_new: CoreBaseModule, origin_new: String) -> void:
+func _init(logger_new: CoreBaseModule, origin_new: String, parent_new: Object) -> void:
logger = logger_new
origin = origin_new
+ parent = parent_new
## Prints a diagnostic message.
func diag(message: String) -> void: logger.diag(origin, message)
diff --git a/src/core.gd b/src/core.gd
index 5a1daad..0e876c0 100644
--- a/src/core.gd
+++ b/src/core.gd
@@ -36,6 +36,10 @@ const modules: Array[String] = [ "logger", "misc", "sms", "logui", "erm", "stora
## CORE's configuration object.[br]
## [b]NEVER access this yourself! To change the configuration use [method reload_configuration] instead.[/b]
var config: CoreConfiguration
+## The framework scheduler.[br]
+## Performs various maintenance tasks every minute.
+## [b]Danger: [i]Don't modify this.[/i][/b]
+var scheduler: Timer
## The 'Logger' module
var logger: CoreBaseModule
## The 'Miscellaneous' module
@@ -73,6 +77,7 @@ func _init(new_config: CoreConfiguration = CoreConfiguration.new()) -> void:
reload_configuration(new_config)
initialize_modules()
apply_configuration()
+ initialize_scheduler()
## Handles the initialization part. Injects the builtin modules into the SceneTree and makes sure custom modules can be loaded properly.[br]
## [b]Danger: [i]Don't call this.[/i][/b]
@@ -80,7 +85,8 @@ func _ready() -> void:
inject_modules()
custom_modules_node.name = "Custom Modules"
add_child(custom_modules_node)
- loggeri = logger.get_instance(basepath.replace("res://", "") + "src/core.gd")
+ loggeri = logger.get_instance(basepath.replace("res://", "") + "src/core.gd", self)
+ add_child(scheduler)
## Initializes all built-in modules during the preinitialization phase.[br]
## [b]Danger: [i]Don't call this.[/i][/b]
@@ -90,13 +96,29 @@ func initialize_modules() -> void:
get(module).name = module
get(module).set_script(load(basepath + "src/" + module + ".gd"))
get(module).core = self
- get(module).loggeri = logger.get_instance(basepath.replace("res://", "") + "src/" + module + ".gd")
+ get(module).loggeri = logger.get_instance(basepath.replace("res://", "") + "src/" + module + ".gd", get(module))
get(module)._initialize()
## Injects CORE's builtin modules into the SceneTree.[br]
## [b]Danger: [i]Don't call this.[/i][/b]
func inject_modules() -> void: for module in modules: add_child(get(module))
+## Initializes the framework scheduler.
+## [b]Danger: [i]Don't call this.[/i][/b]
+func initialize_scheduler() -> void:
+ scheduler = Timer.new()
+ scheduler.name = "Scheduler"
+ scheduler.autostart = true
+ scheduler.one_shot = false
+ scheduler.paused = false
+ scheduler.wait_time = 60
+ scheduler.process_mode = Node.PROCESS_MODE_ALWAYS
+ scheduler.connect("timeout", func() -> void:
+ loggeri.verb("Running scheduler tasks")
+ for module in modules: await get(module)._schedule()
+ for module in custom_modules_node.get_children(): await module._schedule()
+ )
+
## Waits for all modules to fully initialize.[br]
## [br]
## This ensures that all modules are fully initialized and ready for usage.[br]
@@ -128,6 +150,33 @@ func complete_init(no_success_message: bool = false) -> void:
await get_tree().process_frame
if !no_success_message: loggeri.info("Initialized CORE successfully")
+# +++ configuration +++
+## Loads a (new) configuration object and applies it to all modules.
+func reload_configuration(new_config: CoreConfiguration = CoreConfiguration.new()) -> void:
+ var initialized = config != null
+ if initialized: loggeri.verb("Reloading CORE's configuration")
+ if config != null: config.queue_free()
+ config = new_config
+ if is_devmode(): # Override configuration in development mode
+ config.logger_level = CoreTypes.LoggerLevel.DIAG
+ if initialized: loggeri.verb("Overrode configuration (development mode)")
+ if initialized: apply_configuration()
+
+## Applies the a configuration.[br]
+## [b]Danger: [i]Don't call this.[/i][/b]
+func apply_configuration() -> void:
+ if loggeri != null: loggeri.verb("Applying configuration")
+ if is_devmode(): if loggeri != null: loggeri.warn("The CORE Framework is in development mode. Here be dragons!")
+ if config.headless: loggeri.warn("CORE is in headless mode. Certain modules will not work as expected.")
+ if !config.custom_modules:
+ if loggeri != null: loggeri.verb("Removing all custom modules (custom modules support is disabled)")
+ for module in custom_modules: unregister_custom_module(module)
+ for module in modules: get(module)._pull_config()
+ if config.custom_modules:
+ for module in custom_modules:
+ if loggeri != null: loggeri.diag("Updating configuration for custom module \"" + module.name + "\"")
+ module._pull_config()
+
# +++ custom module support +++
## Registers a new custom module.
func register_custom_module(module_name: String, module_origin: String, module_class: CoreBaseModule) -> bool:
@@ -141,7 +190,7 @@ func register_custom_module(module_name: String, module_origin: String, module_c
loggeri.diag("Updating variables")
module_class.name = module_name
module_class.core = self
- module_class.loggeri = logger.get_instance(module_origin)
+ module_class.loggeri = logger.get_instance(module_origin, module_class)
module_class.loggeri.framework = true
loggeri.diag("Adding module to SceneTree")
custom_modules_node.add_child(module_class)
@@ -175,33 +224,6 @@ func get_custom_module(module_name: String) -> CoreBaseModule:
return null
return custom_modules[module_name]
-# +++ configuration +++
-## Loads a (new) configuration object and applies it to all modules.
-func reload_configuration(new_config: CoreConfiguration = CoreConfiguration.new()) -> void:
- var initialized = config != null
- if initialized: loggeri.verb("Reloading CORE's configuration")
- if config != null: config.queue_free()
- config = new_config
- if is_devmode(): # Override configuration in development mode
- config.logger_level = CoreTypes.LoggerLevel.VERB
- if initialized: loggeri.verb("Overrode configuration (development mode)")
- if initialized: apply_configuration()
-
-## Applies the a configuration.[br]
-## [b]Danger: [i]Don't call this.[/i][/b]
-func apply_configuration() -> void:
- if loggeri != null: loggeri.verb("Applying configuration")
- if is_devmode(): if loggeri != null: loggeri.warn("The CORE Framework is in development mode. Here be dragons!")
- if config.headless: loggeri.warn("CORE is in headless mode. Certain modules will not work as expected.")
- if !config.custom_modules:
- if loggeri != null: loggeri.verb("Removing all custom modules (custom modules support is disabled)")
- for module in custom_modules: unregister_custom_module(module)
- for module in modules: get(module)._pull_config()
- if config.custom_modules:
- for module in custom_modules:
- if loggeri != null: loggeri.diag("Updating configuration for custom module \"" + module.name + "\"")
- module._pull_config()
-
# +++ etc ++
## 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.
diff --git a/src/logger.gd b/src/logger.gd
index c1d8231..f8035e0 100644
--- a/src/logger.gd
+++ b/src/logger.gd
@@ -23,6 +23,11 @@ extends CoreBaseModule
signal log_event
+## Keeps track of all logger instances.
+## Unused instances will be cleaned periodically.
+## [b]Danger: [i]Don't modify this.[/i][/b]
+var instances: Array[CoreLoggerInstance] = []
+
## The minimum log level you want to be displayed.
var config_level: CoreTypes.LoggerLevel
## Determines if the logger's output should be colored.
@@ -36,6 +41,14 @@ var config_newlines_override: bool
var config_newlines_sizelimit: int
# +++ module +++
+func _schedule() -> void:
+ for instance in instances:
+ if !is_instance_valid(instance): continue
+ if !is_instance_valid(instance.parent):
+ loggeri.diag("Removing instance '" + instance.name + "'")
+ instance.queue_free()
+ instances.remove_at(instances.find(instance))
+
func _pull_config() -> void:
config_level = core.config.logger_level
config_colored = core.config.logger_colored
@@ -203,4 +216,8 @@ func is_level_allowed(level: CoreTypes.LoggerLevel) -> bool:
else: return false
## Returns a [class CoreLoggerInstance], which is a fancy word meaning you don't need to pass [code]origin[/code] every time you want to log something.
-func get_instance(origin: String) -> CoreLoggerInstance: return CoreLoggerInstance.new(self, origin)
+func get_instance(origin: String, parent: Object) -> CoreLoggerInstance:
+ var instance: CoreLoggerInstance = CoreLoggerInstance.new(self, origin, parent)
+ instance.name = "CoreLoggerInstance -> " + str(parent) + " (" + origin + ")"
+ instances.append(instance)
+ return instance