CORE/cml.gd
JeremyStar™ 99703cf03e CORE rewrite (#1)
Reviewed-on: StarOpenSource/core#1

Rewrote CORE and improved the startup process and startup time significantly. The documentation has been beefed up too and is now much better. Existing projects may need major refactoring however.
Co-authored-by: JeremyStarTM <jeremystartm@staropensource.de>
Co-committed-by: JeremyStarTM <jeremystartm@staropensource.de>
2023-08-25 14:34:57 +02:00

127 lines
6.9 KiB
GDScript

######################################
# THE CORE FRAMEWORK #
# MADE BY THE STAROPENSOURCE PROJECT #
# AND CONTRIBUTERS (THANK YOU!) #
# #
# COPYRIGHT 2023 THE STAROPENSOURCE #
# PROJECT AND CONTRIBUTERS #
# #
# LICENSED UNDER THE GNU GENERAL #
# PUBLIC LICENSE VERSION 3 (ONLY) #
######################################
extends Node
# CORE modules
var core: Node = null
var logger: Node = null
# Configuration
var config_loadpath = null
var config_wanted_name = null
var config_wanted_version = null
var config_wanted_api = null
# Modification list
var modlist = []
# Hook list
var hooks = {}
func load_mods() -> Array:
if core.protection_mode: return []
var errors: Array = []
for directory in DirAccess.get_directories_at(config_loadpath):
var return_value: String = await load_mod(directory)
if return_value != "":
errors.append(return_value)
return errors
func load_mod(mod_name:String) -> String:
logger.info("CORE/cml.gd","Loading modification \"" + mod_name + "\"")
if modlist.has(mod_name):
logger.error("CORE/cml.gd","Modification \"" + mod_name + "\" is already loaded")
if !FileAccess.file_exists(config_loadpath + "/" + mod_name + "/" + config_wanted_name + ".coremod"):
logger.error("CORE/cml.gd","Modification located at " + config_loadpath + "/" + mod_name + " does not contain a " + config_wanted_name + ".coremod file")
return "Modification located at " + config_loadpath + "/" + mod_name + " does not contain a " + config_wanted_name + ".coremod file"
var modinfo_raw: FileAccess = FileAccess.open(config_loadpath + "/" + mod_name + "/" + config_wanted_name + ".coremod",FileAccess.READ)
var modinfo: Dictionary = JSON.parse_string(modinfo_raw.get_as_text())
modinfo_raw.close()
if modinfo == null:
logger.error("CORE/cml.gd","Failed modinfo parsing for modification located at " + config_loadpath + "/" + mod_name)
return "Failed modinfo parsing for modification located at " + config_loadpath + "/" + mod_name
if !modinfo.has_all(["wanted","mod"]):
logger.error("CORE/cml.gd","The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain wanted, mod or both")
return "The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain wanted, mod or both"
if !modinfo["wanted"].has_all(["min_version","max_version","min_api","max_api"]):
logger.error("CORE/cml.gd","The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain wanted.min_version, wanted.max_version, wanted.min_api or wanted.max_api or some combination of them.")
return "The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain wanted.min_version, wanted.max_version, wanted.min_api or wanted.max_api or some combination of them."
if !modinfo["mod"].has_all(["name","version","authors","license","entrypoint"]):
logger.error("CORE/cml.gd","The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain mod.name, mod.version, mod.authors, mod.license or mod.entrypoint or some combination of them.")
return "The modinfo of modification located at " + config_loadpath + "/" + mod_name + " does not contain mod.name, mod.version, mod.authors, mod.license or mod.entrypoint or some combination of them."
if not modinfo["wanted"]["min_version"] <= config_wanted_version or not modinfo["wanted"]["max_version"] >= config_wanted_version:
logger.error("CORE/cml.gd","The modification \"" + modinfo["mod"]["name"] + " does not match wanted version " + str(config_wanted_version))
return "The modification \"" + modinfo["mod"]["name"] + " does not match wanted version " + str(config_wanted_version)
if not modinfo["wanted"]["min_api"] <= config_wanted_api or not modinfo["wanted"]["max_api"] >= config_wanted_api:
logger.error("CORE/cml.gd","The modification \"" + modinfo["mod"]["name"] + " does not match wanted api " + str(config_wanted_api))
return "The modification \"" + modinfo["mod"]["name"] + " does not match wanted api " + str(config_wanted_api)
if !FileAccess.file_exists(config_loadpath + "/" + mod_name + "/" + modinfo["mod"]["entrypoint"]):
logger.error("CORE/cml.gd","The entrypoint for the modification \"" + modinfo["mod"]["name"] + "\" located at \"" + config_loadpath + "/" + mod_name + "/" + modinfo["mod"]["entrypoint"] + "\" does not exist")
return "The entrypoint for the modification \"" + modinfo["mod"]["name"] + "\" located at \"" + config_loadpath + "/" + mod_name + "/" + modinfo["mod"]["entrypoint"] + "\" does not exist"
var entrypoint_script: Script = ResourceLoader.load(config_loadpath + "/" + mod_name + "/" + modinfo["mod"]["entrypoint"])
var entrypoint: Node = Node.new()
entrypoint.name = mod_name
entrypoint.set_script(entrypoint_script)
get_tree().root.add_child(entrypoint)
var mod_err: String = await get_node("/root/" + mod_name)._start()
if mod_err == "":
modlist.append(mod_name)
return ""
else:
get_tree().root.remove_child(entrypoint)
return "Modification \"" + mod_name + "\" could not be loaded as it returned this error: " + mod_err
func unload_mods() -> void:
for mod in modlist:
await unload_mod(mod)
func unload_mod(mod_name:String) -> void:
logger.info("CORE/cml.gd","Unloading modification \"" + mod_name + "\"")
if !modlist.has(mod_name):
logger.error("CORE/cml.gd","Modification \"" + mod_name + "\" is not loaded")
if get_tree().root.get_node_or_null(mod_name) == null:
core.exception("CORE/cml.gd","Could not locate mod entrypoint script for mod \"" + mod_name + "\" during unload")
return
await get_node("/root/" + mod_name)._stop()
get_tree().root.remove_child(get_node("/root/" + mod_name))
modlist.erase(mod_name)
func register_hook(mod_name:String,hook_name:String,hook_action:int,method:Callable) -> bool:
if !modlist.has(mod_name):
core.exception("CORE/cml.gd","Failed registering hook \"" + hook_name + "\" for mod \"" + mod_name + "\" as it does not exist or is not loaded")
return false
core.exception("CORE/cml.gd","Function not implemented")
return true
func get_list() -> Array:
return modlist
func get_info(mod_name:String) -> Dictionary:
if !modlist.has(mod_name):
return {"error":"Modification \"" + mod_name + "\" is not loaded"}
var modinfo_raw: FileAccess = FileAccess.open(config_loadpath + "/" + mod_name + "/" + config_wanted_name + ".coremod",FileAccess.READ)
var modinfo: Dictionary = JSON.parse_string(modinfo_raw.get_as_text())
modinfo_raw.close()
if modinfo == null:
return {"error":"Failed parsing modinfo for modification \"" + mod_name + "\""}
return modinfo
func load_configuration() -> void:
if core.protection_mode: return
config_loadpath = core.config.cml_loadpath
config_wanted_name = core.config.cml_wanted_name
config_wanted_version = core.config.cml_wanted_version
if core.config.get("cml_wanted_api") == null:
config_wanted_api = config_wanted_version
else:
config_wanted_api = core.config.cml_wanted_api