2024-03-22 21:52:49 +01:00
# CORE FRAMEWORK SOURCE FILE
# Copyright (c) 2024 The StarOpenSource Project & Contributors
# Licensed under the GNU Affero General Public License v3
#
# This program is free software: you can redistribute it and/or modify
# 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.
#
# 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
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# 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
## serving the CORE Framework to the developer.
extends Node
class_name Core
# Constants
2024-03-28 13:05:12 +01:00
## The version number
const version_version : int = 1
## The version type
2024-03-22 21:52:49 +01:00
const version_type : CoreTypes . VersionType = CoreTypes . VersionType . BETA
2024-03-28 13:05:12 +01:00
## The version type number. Resets on every new version and version type.
const version_typerelease : int = 4
2024-03-22 21:52:49 +01:00
# Modules
## Use this to access CORE's logging implementation.
var logger : CoreBaseModule
## Use this to access various useful functions.
var misc : CoreBaseModule
## Use this to access the scene management system.
var sms : CoreBaseModule
## Use this to access the graphical log. Serves no importance to you (probably).
var logui : CoreBaseModule
## Use this to access CORE's builtin HTTP request maker.
var edl : CoreBaseModule
## Use this to access configuration and settings files easily.
var storage : CoreBaseModule
# Variables
## Contains CORE's load path
var basepath : String
## Holds the configuration[br]
## [br]
## [b]NEVER access this yourself. To change the configuration file, use [method Core.reload_configuration] instead.[/b]
var config : CoreConfiguration
## Contains all loaded custom modules.
var custom_modules : Dictionary = { }
## Contains the custom modules node.
var custom_modules_node : Node
# Preinitialization
func _init ( new_config : CoreConfiguration = CoreConfiguration . new ( ) ) - > void :
name = " CORE "
if ! check_godot_version ( ) : return
if ! determine_basepath ( ) : queue_free ( )
custom_modules_node = Node . new ( )
reload_configuration ( new_config )
initialize_modules ( )
apply_configuration ( )
# Initialization
func _ready ( ) - > void :
inject_modules ( )
custom_modules_node . name = " Custom Modules "
add_child ( custom_modules_node )
# Initialize modules
## Initializes all modules during the first initialization phase.[br]
## [br]
## [b]NEVER call this yourself! You will break everything and risk a crash![/b]
func initialize_modules ( ) - > void :
# Create Nodes
logger = CoreBaseModule . new ( )
misc = CoreBaseModule . new ( )
sms = CoreBaseModule . new ( )
logui = CoreBaseModule . new ( )
edl = CoreBaseModule . new ( )
storage = CoreBaseModule . new ( )
# Set names
logger . name = " Logger "
misc . name = " Misc "
sms . name = " SceneManagementSystem "
logui . name = " LogUI "
edl . name = " EasyDownLoader "
storage . name = " Storage "
# Set scripts
2024-03-28 13:05:12 +01:00
logger . set_script ( ResourceLoader . load ( basepath + " src/logger.gd " ) )
misc . set_script ( ResourceLoader . load ( basepath + " src/misc.gd " ) )
sms . set_script ( ResourceLoader . load ( basepath + " src/sms.gd " ) )
logui . set_script ( ResourceLoader . load ( basepath + " src/logui.gd " ) )
edl . set_script ( ResourceLoader . load ( basepath + " src/edl.gd " ) )
storage . set_script ( ResourceLoader . load ( basepath + " src/storage.gd " ) )
2024-03-22 21:52:49 +01:00
# Set reference to self
logger . core = self
misc . core = self
sms . core = self
logui . core = self
edl . core = self
storage . core = self
# Call _initialize() (workaround as modules cannot access "core" during _init())
logger . _initialize ( )
misc . _initialize ( )
sms . _initialize ( )
logui . _initialize ( )
edl . _initialize ( )
storage . _initialize ( )
# Inject modules into the SceneTree
## Injects CORE's builtin modules into the SceneTree.[br]
## [br]
## [b]NEVER call this yourself! You will break everything and risk a crash![/b]
func inject_modules ( ) - > void :
add_child ( logger )
add_child ( misc )
add_child ( sms )
add_child ( logui )
add_child ( edl )
add_child ( storage )
# Wait for all modules to be fully initialized
## Wait for all builtin modules to be fully initialized.[br]
## [br]
## This ensures that all of CORE's builtin modules are fully initialized and ready.
## [b]Not calling this function during startup may lead to runtime issues.[/b]
func complete_init ( no_success : bool = false ) - > void :
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
if ! logger . initialized : modsinit_builtin . append ( " logger " )
if ! misc . initialized : modsinit_builtin . append ( " misc " )
if ! sms . initialized : modsinit_builtin . append ( " sms " )
if ! logui . initialized : modsinit_builtin . append ( " logui " )
if ! edl . initialized : modsinit_builtin . append ( " edl " )
if ! storage . initialized : modsinit_builtin . append ( " storage " )
# 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: " )
if modsinit_builtin . size ( ) != 0 :
print ( " Builtin: " + str ( modsinit_builtin ) )
if modsinit_custom . size ( ) != 0 :
print ( " Custom: " + str ( modsinit_custom ) )
await get_tree ( ) . create_timer ( 1 ) . timeout
# Initialization complete
await get_tree ( ) . process_frame
if ! no_success : logger . infof ( " Core " , " Initialized CORE successfully " )
# Registers a custom module
## Registers a new custom module.
func register_custom_module ( module_name : String , module_class : CoreBaseModule ) - > bool :
logger . verbf ( " Core " , " Registering new custom module \" " + module_name + " \" " )
if ! config . custom_modules :
logger . errorf ( " Core " , " Registering module failed: Custom module support is disabled. " )
return false
if custom_modules . has ( module_name ) :
logger . errorf ( " Core " , " Registering module failed: A custom module with the name \" " + module_name + " \" already exists. " )
return false
module_class . name = module_name
logger . diagf ( " Core " , " Updating variables " )
module_class . core = self
logger . diagf ( " Core " , " Adding module to SceneTree " )
custom_modules_node . add_child ( module_class )
logger . diagf ( " Core " , " Merging module with custom_modules " )
custom_modules . merge ( { module_name : module_class } )
logger . diagf ( " Core " , " Initializing custom module " )
module_class . _initialize ( )
logger . diagf ( " Core " , " Updating custom module configuration " )
module_class . _pull_config ( )
return true
# Unregisters a custom module
## Unregisters a custom module, making it no longer function.
func unregister_custom_module ( module_name : String ) - > void :
logger . verbf ( " Core " , " Unregistering custom module \" " + module_name + " \" " )
if ! custom_modules . has ( module_name ) :
logger . errorf ( " Core " , " Unregistering module failed: A custom module with the name \" " + module_name + " \" does not exist. " )
return
custom_modules_node . remove_child ( get_custom_module ( module_name ) )
custom_modules . erase ( module_name )
# Returns a custom module
## Returns a loaded custom module for access.
func get_custom_module ( module_name : String ) - > CoreBaseModule :
logger . diagf ( " Core " , " Getting custom module \" " + module_name + " \" " )
if ! custom_modules . has ( module_name ) :
logger . errorf ( " Core " , " Getting module failed: A custom module with the name \" " + module_name + " \" does not exist. " )
return null
return custom_modules [ module_name ]
# (Re-)Load configuration
## Loads a (new) configuration file and applies it to all modules.
func reload_configuration ( new_config : CoreConfiguration = CoreConfiguration . new ( ) ) - > void :
var initialized = config != null
if initialized : logger . verbf ( " Core " , " Reloading CORE ' s configuration " )
config = new_config
if is_devmode ( ) : # Override configuration in development mode
config . logger_level = CoreTypes . LoggerLevel . VERB
if initialized : logger . verbf ( " Core " , " Overrode configuration (development mode) " )
if initialized : apply_configuration ( )
# Call _pull_config() functions
## Applies the newly applied configuration.[br]
## [br]
## [b]NEVER call this yourself unless you know what you are doing![/b]
func apply_configuration ( ) - > void :
logger . verbf ( " Core " , " Applying configuration " )
if is_devmode ( ) : logger . warnf ( " Core " , " The CORE Framework is in development mode. Here be dragons! " )
if config . headless : logger . warnf ( " Core " , " CORE is in headless mode. Certain modules will not work as expected. " )
edl . _pull_config ( )
if ! config . custom_modules :
logger . verbf ( " Core " , " Removing all custom modules (custom modules support is disabled) " )
for module in custom_modules : unregister_custom_module ( module )
logger . _pull_config ( )
misc . _pull_config ( )
sms . _pull_config ( )
logui . _pull_config ( )
if config . custom_modules :
for module in custom_modules :
logger . diagf ( " Core " , " Updating configuration for custom module \" " + module . name + " \" " )
module . _pull_config ( )
# Return development mode status
## Returns if the CORE Framework is in development mode.
func is_devmode ( ) - > bool :
return config . debugging and basepath == " res:// " and OS . is_debug_build ( )
# Replaces variables with human-friendly strings
## Replaces placeholders with human-friendly strings You can use the following placeholders:[br]
## - [code]%release%[/code]: Returns the release number.[br]
## - [code]%release_type%[/code]: Returns the typerelease number[br]
## - [code]%release_semantic%[/code]: Returns the result of [method Core.get_version_semantic], example [i]5.2.3[/i][br]
## - [code]%type%[/code]: Returns the release type as a word, for example [i]Release Candidate[/i][br]
## - [code]%type_technical%[/code]: Returns the release type as one or two lowercase letters, for example [i]rc[/i][br]
## - [code]%devmode%[/code]: Returns the development mode status[br]
## - [code]%headless%[/code]: Returns the headless mode status
func get_formatted_string ( string : String ) - > String :
# Version strings
2024-03-28 13:05:12 +01:00
string = string . replace ( " % version % " , str ( version_version ) )
string = string . replace ( " % version_type % " , str ( version_typerelease ) )
2024-03-22 21:52:49 +01:00
var semantic_version : Array [ int ] = get_version_semantic ( )
2024-03-28 13:05:12 +01:00
string = string . replace ( " % version_semantic % " , str ( semantic_version [ 0 ] ) + " . " + str ( semantic_version [ 1 ] ) + " . " + str ( semantic_version [ 2 ] ) )
2024-03-22 21:52:49 +01:00
match ( version_type ) :
CoreTypes . VersionType . RELEASE :
string = string . replace ( " % type % " , " Release " )
string = string . replace ( " % type_technical % " , " r " )
CoreTypes . VersionType . RELEASECANDIDATE :
string = string . replace ( " % type % " , " Release Candidate " )
string = string . replace ( " % type_technical % " , " rc " )
CoreTypes . VersionType . BETA :
string = string . replace ( " % type % " , " Beta " )
string = string . replace ( " % type_technical % " , " b " )
CoreTypes . VersionType . ALPHA :
string = string . replace ( " % type % " , " Alpha " )
string = string . replace ( " % type_technical % " , " a " )
_ : await logger . crashf ( " Core " , " Invalid version type " + str ( version_type ) , true )
# 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 " )
# Custom module support
if config . custom_modules : string = string . replace ( " %c ustommodules % " , " Enabled " )
else : string = string . replace ( " %c ustommodules % " , " Disabled " )
return string
# Return CORE's version in the semantic versioning scheme
## Returns CORE's versioning scheme into the semantic versioning scheme.[br]
## The first integer contains the release number, the second integer contains the release 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 typerelease number.
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 13:05:12 +01:00
return [ version_version , version_type_int , version_typerelease ]
2024-03-22 21:52:49 +01:00
# Determines CORE's installation/base path
## Determines CORE's installation/base path[br]
## [br]
## [b]Calling this function is likely to be safe, but shouldn't be done nonetheless![/b]
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
_ :
printerr ( " The CORE Framework does not support Godot versions older or newer than 4.x.x " )
return false
match ( version [ " minor " ] ) :
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. " )
2 : pass
_ :
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
return true