2024-02-12 19:47:28 +01:00
# CORE FRAMEWORK SOURCE FILE
# Copyright (c) 2024 The StarOpenSource Project & Contributors
2024-03-03 18:53:09 +01:00
# Licensed under the GNU Affero General Public License v3
2024-02-12 19:47:28 +01:00
#
# This program is free software: you can redistribute it and/or modify
2024-03-03 18:53:09 +01:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2024-02-12 19:47:28 +01:00
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2024-03-03 18:53:09 +01:00
# GNU Affero General Public License for more details.
2024-02-12 19:47:28 +01:00
#
2024-03-03 18:53:09 +01:00
# You should have received a copy of the GNU Affero General Public License
2024-02-12 19:47:28 +01:00
# along with this program. If not, see <https://www.gnu.org/licenses/>.
## Initializes and manages the framework.
##
## The [b]CORE Object[/b] is responsible for initializing, managing and
2024-04-08 02:43:31 +02:00
## serving the CORE Framework.
2024-02-04 21:36:30 +01:00
extends Node
class_name Core
2024-04-08 02:43:31 +02:00
# Versioning
2024-03-28 12:54:12 +01:00
## The version number
const version_version : int = 1
2024-04-08 02:43:31 +02:00
## The version type. See [enum CoreTypes.VersionType] for more information.
2024-02-10 21:56:04 +01:00
const version_type : CoreTypes . VersionType = CoreTypes . VersionType . BETA
2024-03-28 12:54:12 +01:00
## The version type number. Resets on every new version and version type.
2024-04-08 02:43:31 +02:00
const version_typerelease : int = 6
2024-02-04 21:36:30 +01:00
# Modules
2024-04-08 02:43:31 +02:00
## Used internally for loading, managing and unloading modules.
2024-03-31 17:35:09 +02:00
const modules : Array [ String ] = [ " logger " , " misc " , " sms " , " logui " , " erm " , " storage " ]
2024-04-08 02:43:31 +02:00
## CORE's configuration object.[br]
## [b]NEVER access this yourself! To change the configuration use [method reload_configuration] instead.[/b]
var config : CoreConfiguration
## The 'Logger' module
2024-02-04 21:36:30 +01:00
var logger : CoreBaseModule
2024-04-08 02:43:31 +02:00
## The 'Miscellaneous' module
2024-02-04 21:36:30 +01:00
var misc : CoreBaseModule
2024-04-08 02:43:31 +02:00
## The 'Scene Management System' module
2024-02-09 21:18:14 +01:00
var sms : CoreBaseModule
2024-04-08 02:43:31 +02:00
## The 'Log UI' module. Not important for you, it just displays the log graphically :3
2024-02-04 21:36:30 +01:00
var logui : CoreBaseModule
2024-04-08 02:43:31 +02:00
## The 'Easy Request Maker' module (formerly 'Easy DownLoader')
2024-03-31 17:35:09 +02:00
var erm : CoreBaseModule
2024-04-08 02:43:31 +02:00
## The 'Storage' module
2024-03-18 03:23:27 +01:00
var storage : CoreBaseModule
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
# /etc/
## Stores the path to CORE's installation directory.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't modify this.[/i][/b]
2024-02-04 21:36:30 +01:00
var basepath : String
2024-04-08 02:43:31 +02:00
## Contains a list of all loaded custom modules.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't modify this.[/i][/b]
2024-02-05 18:03:16 +01:00
var custom_modules : Dictionary = { }
2024-04-08 02:43:31 +02:00
## Contains the node holding all custom modules as children.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't modify this.[/i][/b]
2024-02-09 15:30:20 +01:00
var custom_modules_node : Node
2024-04-08 21:30:24 +02:00
## The CORE Object's logger instance.
## [b]Danger: [i]Don't modify this.[/i][/b]
var loggeri : CoreLoggerInstance
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
# +++ initialization +++
## Handles the preinitialization part. Does stuff like checking the engine version, loading the config and loading all modules into memory.
2024-02-04 21:36:30 +01:00
func _init ( new_config : CoreConfiguration = CoreConfiguration . new ( ) ) - > void :
name = " CORE "
2024-03-03 19:08:20 +01:00
if ! check_godot_version ( ) : return
2024-02-04 21:36:30 +01:00
if ! determine_basepath ( ) : queue_free ( )
2024-02-09 15:30:20 +01:00
custom_modules_node = Node . new ( )
2024-02-04 21:36:30 +01:00
reload_configuration ( new_config )
initialize_modules ( )
apply_configuration ( )
2024-04-08 02:43:31 +02:00
## Handles the initialization part. Injects the builtin modules into the SceneTree and makes sure custom modules can be loaded properly.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't call this.[/i][/b]
2024-02-04 21:36:30 +01:00
func _ready ( ) - > void :
inject_modules ( )
2024-02-09 22:59:43 +01:00
custom_modules_node . name = " Custom Modules "
2024-02-09 15:30:20 +01:00
add_child ( custom_modules_node )
2024-04-08 21:45:46 +02:00
loggeri = logger . get_instance ( basepath . replace ( " res:// " , " " ) + " src/core.gd " )
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
## Initializes all built-in modules during the preinitialization phase.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't call this.[/i][/b]
2024-02-04 21:36:30 +01:00
func initialize_modules ( ) - > void :
2024-03-29 02:40:56 +01:00
for module in modules :
set ( module , CoreBaseModule . new ( ) )
get ( module ) . name = module
get ( module ) . set_script ( load ( basepath + " src/ " + module + " .gd " ) )
get ( module ) . core = self
2024-03-31 11:22:57 +02:00
get ( module ) . loggeri = logger . get_instance ( basepath . replace ( " res:// " , " " ) + " src/ " + module + " .gd " )
2024-03-29 02:40:56 +01:00
get ( module ) . _initialize ( )
2024-02-04 21:36:30 +01:00
2024-02-12 19:47:28 +01:00
## Injects CORE's builtin modules into the SceneTree.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't call this.[/i][/b]
2024-03-29 02:40:56 +01:00
func inject_modules ( ) - > void : for module in modules : add_child ( get ( module ) )
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
## Waits for all modules to fully initialize.[br]
2024-03-22 21:12:11 +01:00
## [br]
2024-04-08 02:43:31 +02:00
## This ensures that all modules are fully initialized and ready for usage.[br]
## [i][b]Not calling this function during startup may lead to runtime issues.[/b][/i]
func complete_init ( no_success_message : bool = false ) - > void :
2024-03-22 21:12:11 +01:00
var modsinit_builtin : Array [ String ] = [ " workaround " ]
var modsinit_custom : Array [ String ] = [ " workaround " ]
while modsinit_builtin . size ( ) != 0 and modsinit_custom . size ( ) != 0 :
# Clear arrays
modsinit_builtin = [ ]
modsinit_custom = [ ]
# Check builtin modules
2024-03-29 02:40:56 +01:00
for module in modules : if ! get ( module ) . initialized : modsinit_builtin . append ( module )
2024-03-22 21:12:11 +01:00
# Check custom modules
for module_name in custom_modules :
if ! custom_modules [ module_name ] . initialized : modsinit_custom . append ( module_name )
# Print and sleep
if modsinit_builtin . size ( ) != 0 or modsinit_custom . size ( ) != 0 :
print ( " Waiting for modules to finish initialization: " )
2024-03-29 02:40:56 +01:00
if modsinit_builtin . size ( ) != 0 : print ( " Builtin: " + str ( modsinit_builtin ) )
if modsinit_custom . size ( ) != 0 : print ( " Custom: " + str ( modsinit_custom ) )
2024-03-22 21:12:11 +01:00
await get_tree ( ) . create_timer ( 1 ) . timeout
# Initialization complete
await get_tree ( ) . process_frame
2024-04-08 21:30:24 +02:00
if ! no_success_message : loggeri . info ( " Initialized CORE successfully " )
2024-03-22 21:12:11 +01:00
2024-04-08 02:43:31 +02:00
# +++ custom module support +++
2024-02-12 19:47:28 +01:00
## Registers a new custom module.
2024-03-31 11:22:57 +02:00
func register_custom_module ( module_name : String , module_origin : String , module_class : CoreBaseModule ) - > bool :
2024-04-08 21:30:24 +02:00
loggeri . verb ( " Registering new custom module \" " + module_name + " \" " )
2024-02-10 18:49:25 +01:00
if ! config . custom_modules :
2024-04-08 21:30:24 +02:00
loggeri . error ( " Registering module failed: Custom module support is disabled. " )
2024-02-10 18:49:25 +01:00
return false
2024-02-05 18:03:16 +01:00
if custom_modules . has ( module_name ) :
2024-04-08 21:30:24 +02:00
loggeri . error ( " Registering module failed: A custom module with the name \" " + module_name + " \" already exists. " )
2024-02-10 18:49:25 +01:00
return false
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Updating variables " )
2024-03-31 11:22:57 +02:00
module_class . name = module_name
2024-03-22 21:41:27 +01:00
module_class . core = self
2024-03-31 11:22:57 +02:00
module_class . loggeri = logger . get_instance ( module_origin )
2024-04-08 21:37:39 +02:00
module_class . loggeri . framework = true
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Adding module to SceneTree " )
2024-02-09 15:30:20 +01:00
custom_modules_node . add_child ( module_class )
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Merging module with custom_modules " )
2024-02-05 18:03:16 +01:00
custom_modules . merge ( { module_name : module_class } )
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Initializing custom module " )
2024-02-05 18:03:16 +01:00
module_class . _initialize ( )
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Updating custom module configuration " )
2024-02-05 18:03:16 +01:00
module_class . _pull_config ( )
2024-02-10 18:49:25 +01:00
return true
2024-02-05 18:03:16 +01:00
2024-04-08 02:43:31 +02:00
## Unregisters a custom module, making it no longer available.
2024-02-05 18:03:16 +01:00
func unregister_custom_module ( module_name : String ) - > void :
2024-04-08 21:30:24 +02:00
loggeri . verb ( " Unregistering custom module \" " + module_name + " \" " )
2024-02-05 18:03:16 +01:00
if ! custom_modules . has ( module_name ) :
2024-04-08 21:30:24 +02:00
loggeri . error ( " Unregistering module failed: A custom module with the name \" " + module_name + " \" does not exist. " )
2024-02-05 18:03:16 +01:00
return
2024-03-29 02:40:56 +01:00
var module : Node = get_custom_module ( module_name )
await module . _cleanup ( )
2024-03-31 11:22:57 +02:00
module . loggeri . queue_free ( )
2024-03-29 02:40:56 +01:00
custom_modules_node . remove_child ( module )
2024-02-05 18:03:16 +01:00
custom_modules . erase ( module_name )
2024-03-29 02:40:56 +01:00
module . queue_free ( )
2024-02-05 18:03:16 +01:00
2024-04-08 02:43:31 +02:00
## Returns a registered custom module.[br]
## Please note that you can't get CORE's built-in modules with this function.
2024-02-05 18:03:16 +01:00
func get_custom_module ( module_name : String ) - > CoreBaseModule :
2024-04-08 21:30:24 +02:00
loggeri . diag ( " Getting custom module \" " + module_name + " \" " )
2024-02-05 18:03:16 +01:00
if ! custom_modules . has ( module_name ) :
2024-04-08 21:30:24 +02:00
loggeri . error ( " Getting module failed: A custom module with the name \" " + module_name + " \" does not exist. " )
2024-03-22 21:41:27 +01:00
return null
2024-02-05 18:03:16 +01:00
return custom_modules [ module_name ]
2024-04-08 02:43:31 +02:00
# +++ configuration +++
## Loads a (new) configuration object and applies it to all modules.
2024-02-04 21:36:30 +01:00
func reload_configuration ( new_config : CoreConfiguration = CoreConfiguration . new ( ) ) - > void :
var initialized = config != null
2024-04-08 21:30:24 +02:00
if initialized : loggeri . verb ( " Reloading CORE ' s configuration " )
2024-03-29 02:40:56 +01:00
if config != null : config . queue_free ( )
2024-02-04 21:36:30 +01:00
config = new_config
if is_devmode ( ) : # Override configuration in development mode
config . logger_level = CoreTypes . LoggerLevel . VERB
2024-04-08 21:30:24 +02:00
if initialized : loggeri . verb ( " Overrode configuration (development mode) " )
2024-02-04 21:36:30 +01:00
if initialized : apply_configuration ( )
2024-04-08 02:43:31 +02:00
## Applies the a configuration.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't call this.[/i][/b]
2024-02-04 21:36:30 +01:00
func apply_configuration ( ) - > void :
2024-04-08 21:30:24 +02:00
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. " )
2024-02-10 18:49:25 +01:00
if ! config . custom_modules :
2024-04-08 21:30:24 +02:00
if loggeri != null : loggeri . verb ( " Removing all custom modules (custom modules support is disabled) " )
2024-02-10 18:49:25 +01:00
for module in custom_modules : unregister_custom_module ( module )
2024-03-29 02:40:56 +01:00
for module in modules : get ( module ) . _pull_config ( )
2024-02-10 18:49:25 +01:00
if config . custom_modules :
for module in custom_modules :
2024-04-08 21:30:24 +02:00
if loggeri != null : loggeri . diag ( " Updating configuration for custom module \" " + module . name + " \" " )
2024-02-10 18:49:25 +01:00
module . _pull_config ( )
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
# +++ 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.
func cleanup ( ) - > void :
2024-04-08 21:30:24 +02:00
loggeri . info ( " Cleaning up " )
2024-04-08 02:43:31 +02:00
config . queue_free ( )
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 ( )
for module in custom_modules_node . get_children ( ) : unregister_custom_module ( module . name )
remove_child ( custom_modules_node )
custom_modules_node . queue_free ( )
queue_free ( )
2024-02-12 19:47:28 +01:00
## Returns if the CORE Framework is in development mode.
2024-02-04 21:36:30 +01:00
func is_devmode ( ) - > bool :
2024-02-10 18:49:25 +01:00
return config . debugging and basepath == " res:// " and OS . is_debug_build ( )
2024-02-04 21:36:30 +01:00
2024-04-08 02:43:31 +02:00
## Replaces placeholders with human-friendly strings.[br]
## You can use the following placeholders:[br]
## - [code]%version%[/code]: Returns the version number.[br]
## - [code]%version_type%[/code]: Returns the version type number[br]
## - [code]%version_semantic%[/code]: Returns the result of [method Core.get_version_semantic], example [i]5.2.3[/i][br]
## - [code]%version_type%[/code]: Returns the version type as a word, for example [i]Release Candidate[/i][br]
## - [code]%version_type_technical%[/code]: Returns the version type as one or two lowercase letters, for example [i]rc[/i][br]
2024-03-03 19:08:40 +01:00
## - [code]%devmode%[/code]: Returns the development mode status[br]
2024-04-08 02:43:31 +02:00
## - [code]%headless%[/code]: Returns the headless mode status[br]
## - [code]%custommodules%[/code]: Returns if custom module support is enabled
2024-02-04 21:36:30 +01:00
func get_formatted_string ( string : String ) - > String :
# Version strings
2024-03-28 12:54:12 +01:00
string = string . replace ( " % version % " , str ( version_version ) )
2024-04-08 20:18:49 +02:00
string = string . replace ( " % version_typerelease % " , str ( version_typerelease ) )
2024-02-04 21:36:30 +01:00
var semantic_version : Array [ int ] = get_version_semantic ( )
2024-03-28 12:54:12 +01:00
string = string . replace ( " % version_semantic % " , str ( semantic_version [ 0 ] ) + " . " + str ( semantic_version [ 1 ] ) + " . " + str ( semantic_version [ 2 ] ) )
2024-02-04 21:36:30 +01:00
match ( version_type ) :
CoreTypes . VersionType . RELEASE :
2024-04-08 02:43:31 +02:00
string = string . replace ( " % version_type % " , " Release " )
string = string . replace ( " % version_type_technical % " , " r " )
2024-02-04 21:36:30 +01:00
CoreTypes . VersionType . RELEASECANDIDATE :
2024-04-08 02:43:31 +02:00
string = string . replace ( " % version_type % " , " Release Candidate " )
string = string . replace ( " % version_type_technical % " , " rc " )
2024-02-04 21:36:30 +01:00
CoreTypes . VersionType . BETA :
2024-04-08 02:43:31 +02:00
string = string . replace ( " % version_type % " , " Beta " )
string = string . replace ( " % version_type_technical % " , " b " )
2024-02-04 21:36:30 +01:00
CoreTypes . VersionType . ALPHA :
2024-04-08 02:43:31 +02:00
string = string . replace ( " % version_type % " , " Alpha " )
string = string . replace ( " % version_type_technical % " , " a " )
2024-04-08 21:30:24 +02:00
_ : await loggeri . crash ( " Invalid version type " + str ( version_type ) )
2024-02-04 21:36:30 +01:00
# Development mode
if is_devmode ( ) : string = string . replace ( " %d evmode % " , " Enabled " )
else : string = string . replace ( " %d evmode % " , " Disabled " )
# Headless mode
if config . headless : string = string . replace ( " %he adless % " , " Enabled " )
else : string = string . replace ( " %he adless % " , " Disabled " )
2024-02-10 18:49:25 +01:00
# Custom module support
if config . custom_modules : string = string . replace ( " %c ustommodules % " , " Enabled " )
else : string = string . replace ( " %c ustommodules % " , " Disabled " )
2024-02-04 21:36:30 +01:00
return string
2024-02-12 19:47:28 +01:00
## Returns CORE's versioning scheme into the semantic versioning scheme.[br]
2024-04-08 02:43:31 +02:00
## The first integer contains the version number, the second integer contains the version type ([code]0[/code] for alpha, [code]1[/code] for beta, [code]2[/code] for rc and [code]3[/code] for release and the last integer contains the version type number.
2024-02-04 21:36:30 +01:00
func get_version_semantic ( ) - > Array [ int ] :
var version_type_int : int
match ( version_type ) :
CoreTypes . VersionType . RELEASE : version_type_int = 3
CoreTypes . VersionType . RELEASECANDIDATE : version_type_int = 2
CoreTypes . VersionType . BETA : version_type_int = 1
CoreTypes . VersionType . ALPHA : version_type_int = 0
2024-03-28 12:54:12 +01:00
return [ version_version , version_type_int , version_typerelease ]
2024-02-12 19:56:17 +01:00
2024-04-08 02:43:31 +02:00
## Determines CORE's installation/base path.[br]
2024-04-08 21:19:24 +02:00
## [b]Danger: [i]Don't call this.[/i][/b]
2024-02-12 19:56:17 +01:00
func determine_basepath ( ) - > bool :
if FileAccess . file_exists ( " res://.corebasepath " ) :
basepath = " res:// "
elif FileAccess . file_exists ( " res://CORE/.corebasepath " ) :
basepath = " res://CORE/ "
elif FileAccess . file_exists ( " res://addons/CORE/.corebasepath " ) :
basepath = " res://addons/CORE/ "
else :
assert ( false , " CORE is not located at ' res://CORE/ ' , aborting initialization " )
return false
return true
# Checks Godot's version
## Checks compatibility with the running version.
func check_godot_version ( ) - > bool :
var version : Dictionary = Engine . get_version_info ( )
match ( version [ " major " ] ) :
4 : pass
_ :
2024-03-03 19:08:20 +01:00
printerr ( " The CORE Framework does not support Godot versions older or newer than 4.x.x " )
return false
2024-02-12 19:56:17 +01:00
match ( version [ " minor " ] ) :
2024-03-03 19:08:20 +01:00
0 : printerr ( " The CORE Framework does not support Godot versions older than 4.2.x. Please update to Godot 4.2.x to ensure full compatibility. " )
1 : printerr ( " The CORE Framework does not support Godot versions older than 4.2.x. Please update to Godot 4.2.x to ensure full compatibility. " )
2024-02-12 19:56:17 +01:00
2 : pass
2024-03-03 19:08:20 +01:00
_ :
printerr ( " The CORE Framework does not support Godot versions newer than 4.2.x. Please downgrade to Godot 4.2.x. " )
return false
if version [ " status " ] != " stable " :
printerr ( " The CORE Framework does not support unstable Godot versions. Please switch to Godot stable 4.2.x. " )
return false
2024-02-12 19:56:17 +01:00
return true
2024-04-08 21:42:03 +02:00
## Makes sure for all log messages to be flushed and that CORE is correctly cleaned up.[br]
## Using [method SceneTree.quit] directly may cause various issues.[br]
## [b]Note: [i]Using the [code]await[/code] keyword is required for this function.[/i][/b]
func quit_safely ( exitcode : int = 0 ) - > void :
loggeri . info ( " Shutting down (code " + str ( exitcode ) + " ) " )
await get_tree ( ) . create_timer ( 0.25 ) . timeout
await cleanup ( )
get_tree ( ) . quit ( exitcode )