CORE/src/core.gd

334 lines
14 KiB
GDScript3
Raw Normal View History

# 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
#
# 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.
#
# 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-03-03 18:53:09 +01:00
# 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.
2024-02-04 21:36:30 +01:00
extends Node
class_name Core
# Constants
## The version number
const version_version: int = 1
## The version type
2024-02-10 21:56:04 +01:00
const version_type: CoreTypes.VersionType = CoreTypes.VersionType.BETA
## The version type number. Resets on every new version and version type.
2024-03-27 15:15:46 +01:00
const version_typerelease: int = 4
2024-02-04 21:36:30 +01:00
# Modules
## Use this to access CORE's logging implementation.
2024-02-04 21:36:30 +01:00
var logger: CoreBaseModule
## Use this to access various useful functions.
2024-02-04 21:36:30 +01:00
var misc: CoreBaseModule
## Use this to access the scene management system.
2024-02-09 21:18:14 +01:00
var sms: CoreBaseModule
## Use this to access the graphical log. Serves no importance to you (probably).
2024-02-04 21:36:30 +01:00
var logui: CoreBaseModule
## Use this to access CORE's builtin HTTP request maker.
2024-02-09 22:27:21 +01:00
var edl: CoreBaseModule
2024-03-18 03:23:27 +01:00
## Use this to access configuration and settings files easily.
var storage: CoreBaseModule
2024-02-04 21:36:30 +01:00
# Variables
## Contains CORE's load path
2024-02-04 21:36:30 +01:00
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.
2024-02-09 15:30:20 +01:00
var custom_modules_node: Node
2024-02-04 21:36:30 +01:00
# Preinitialization
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()
# Initialization
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-02-04 21:36:30 +01:00
# 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]
2024-02-04 21:36:30 +01:00
func initialize_modules() -> void:
# Create Nodes
logger = CoreBaseModule.new()
misc = CoreBaseModule.new()
2024-02-09 21:18:14 +01:00
sms = CoreBaseModule.new()
2024-02-04 21:36:30 +01:00
logui = CoreBaseModule.new()
2024-02-09 22:27:21 +01:00
edl = CoreBaseModule.new()
2024-03-18 03:23:27 +01:00
storage = CoreBaseModule.new()
2024-02-04 21:36:30 +01:00
# Set names
logger.name = "Logger"
misc.name = "Misc"
2024-02-09 21:18:14 +01:00
sms.name = "SceneManagementSystem"
2024-02-04 21:36:30 +01:00
logui.name = "LogUI"
2024-02-09 22:27:21 +01:00
edl.name = "EasyDownLoader"
2024-03-18 03:23:27 +01:00
storage.name = "Storage"
2024-02-04 21:36:30 +01:00
# Set scripts
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-02-04 21:36:30 +01:00
# Set reference to self
logger.core = self
misc.core = self
2024-02-09 21:18:14 +01:00
sms.core = self
2024-02-04 21:36:30 +01:00
logui.core = self
2024-02-09 22:27:21 +01:00
edl.core = self
2024-03-18 03:23:27 +01:00
storage.core = self
2024-02-04 21:36:30 +01:00
# Call _initialize() (workaround as modules cannot access "core" during _init())
logger._initialize()
misc._initialize()
2024-02-09 21:18:14 +01:00
sms._initialize()
2024-02-04 21:36:30 +01:00
logui._initialize()
2024-02-09 22:27:21 +01:00
edl._initialize()
2024-03-18 03:23:27 +01:00
storage._initialize()
2024-02-04 21:36:30 +01:00
# 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]
2024-02-04 21:36:30 +01:00
func inject_modules() -> void:
add_child(logger)
add_child(misc)
2024-02-09 21:18:14 +01:00
add_child(sms)
2024-02-04 21:36:30 +01:00
add_child(logui)
2024-02-09 22:27:21 +01:00
add_child(edl)
2024-03-18 03:23:27 +01:00
add_child(storage)
2024-02-04 21:36:30 +01:00
2024-03-22 21:12:11 +01:00
# 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:
2024-03-21 15:47:39 +01:00
logger.verbf("Core", "Registering new custom module \"" + module_name + "\"")
if !config.custom_modules:
2024-03-21 15:47:39 +01:00
logger.errorf("Core", "Registering module failed: Custom module support is disabled.")
return false
if custom_modules.has(module_name):
2024-03-21 15:47:39 +01:00
logger.errorf("Core", "Registering module failed: A custom module with the name \"" + module_name + "\" already exists.")
return false
2024-02-09 15:30:20 +01:00
module_class.name = module_name
2024-03-22 21:41:27 +01:00
logger.diagf("Core", "Updating variables")
module_class.core = self
2024-03-21 15:47:39 +01:00
logger.diagf("Core", "Adding module to SceneTree")
2024-02-09 15:30:20 +01:00
custom_modules_node.add_child(module_class)
2024-03-21 15:47:39 +01:00
logger.diagf("Core", "Merging module with custom_modules")
custom_modules.merge({ module_name: module_class })
2024-03-21 15:47:39 +01:00
logger.diagf("Core", "Initializing custom module")
module_class._initialize()
2024-03-21 15:47:39 +01:00
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:
2024-03-21 15:47:39 +01:00
logger.verbf("Core", "Unregistering custom module \"" + module_name + "\"")
if !custom_modules.has(module_name):
2024-03-21 15:47:39 +01:00
logger.errorf("Core", "Unregistering module failed: A custom module with the name \"" + module_name + "\" does not exist.")
return
2024-02-09 15:30:20 +01:00
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:
2024-03-21 15:47:39 +01:00
logger.diagf("Core", "Getting custom module \"" + module_name + "\"")
if !custom_modules.has(module_name):
2024-03-21 15:47:39 +01:00
logger.errorf("Core", "Getting module failed: A custom module with the name \"" + module_name + "\" does not exist.")
2024-03-22 21:41:27 +01:00
return null
return custom_modules[module_name]
2024-02-04 21:36:30 +01:00
# (Re-)Load configuration
## Loads a (new) configuration file 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-03-21 15:47:39 +01:00
if initialized: logger.verbf("Core", "Reloading CORE's configuration")
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-03-21 15:47:39 +01:00
if initialized: logger.verbf("Core", "Overrode configuration (development mode)")
2024-02-04 21:36:30 +01:00
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]
2024-02-04 21:36:30 +01:00
func apply_configuration() -> void:
2024-03-21 15:47:39 +01:00
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:
2024-03-21 15:47:39 +01:00
logger.verbf("Core", "Removing all custom modules (custom modules support is disabled)")
for module in custom_modules: unregister_custom_module(module)
2024-02-04 21:36:30 +01:00
logger._pull_config()
misc._pull_config()
2024-02-09 21:18:14 +01:00
sms._pull_config()
2024-02-04 21:36:30 +01:00
logui._pull_config()
if config.custom_modules:
for module in custom_modules:
2024-03-21 15:47:39 +01:00
logger.diagf("Core", "Updating configuration for custom module \"" + module.name + "\"")
module._pull_config()
2024-02-04 21:36:30 +01:00
# Return development mode status
## Returns if the CORE Framework is in development mode.
2024-02-04 21:36:30 +01:00
func is_devmode() -> bool:
return config.debugging and basepath == "res://" and OS.is_debug_build()
2024-02-04 21:36:30 +01:00
# 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
2024-02-04 21:36:30 +01:00
func get_formatted_string(string: String) -> String:
# Version strings
string = string.replace("%version%", str(version_version))
string = string.replace("%version_type%", str(version_typerelease))
2024-02-04 21:36:30 +01:00
var semantic_version: Array[int] = get_version_semantic()
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:
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")
2024-03-21 15:47:39 +01:00
_: await logger.crashf("Core", "Invalid version type " + str(version_type), true)
2024-02-04 21:36:30 +01:00
# Development mode
if is_devmode(): string = string.replace("%devmode%", "Enabled")
else: string = string.replace("%devmode%", "Disabled")
# Headless mode
if config.headless: string = string.replace("%headless%", "Enabled")
else: string = string.replace("%headless%", "Disabled")
# Custom module support
if config.custom_modules: string = string.replace("%custommodules%", "Enabled")
else: string = string.replace("%custommodules%", "Disabled")
2024-02-04 21:36:30 +01:00
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.
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
return [version_version, version_type_int, version_typerelease]
2024-02-12 19:56:17 +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
_:
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