From d2735361b06b4a07a70e0e2cbc27c8116b09f931 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Tue, 9 Apr 2024 20:13:49 +0200 Subject: [PATCH] Introduce scheduler and add CoreLoggerInstance cleanup (#17) --- Test.gd | 3 +- docs/docs/reference/core.md | 6 ++ docs/docs/reference/corebasemodule.md | 3 + docs/docs/reference/logger.md | 10 +++- docs/docs/reference/loggerinstance.md | 9 ++- src/classes/basemodule.gd | 3 + src/classes/loggerinstance.gd | 8 ++- src/core.gd | 82 +++++++++++++++++---------- src/logger.gd | 19 ++++++- 9 files changed, 105 insertions(+), 38 deletions(-) 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