Update module logging and update many log calls

This commit firstly removes the 'logger' variable in CoreBaseModule, secondly renames 'loggeri' to 'logger' in CoreBaseModule, effectively replacing it, and thirdly it forces using 'stringify_variables' onto all log calls.
This commit is contained in:
JeremyStar™ 2024-04-25 20:20:34 +02:00
parent 53981c55f9
commit acc305d3db
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
8 changed files with 98 additions and 93 deletions

View file

@ -24,10 +24,8 @@ class_name CoreBaseModule
## Contains a reference to the CORE Object.
var core: Core
## Set to CORE's logger implementation.
@onready var logger: CoreBaseModule = core.logger
## Set to a [class CoreLoggerInstance] with the path you supplied to [method Core.register_custom_module]. You should use this over [code]logger[/code].
var loggeri: CoreLoggerInstance
## Set to a [class CoreLoggerInstance] with the path you supplied to [method Core.register_custom_module].
var logger: CoreLoggerInstance
## Marks a module as fully initialized and ready. **Don't forget to set this!**
var initialized: bool = false

View file

@ -119,7 +119,7 @@ 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))
get(module).logger = logger.get_instance(basepath.replace("res://", "") + "src/" + module + ".gd", get(module))
get(module)._initialize()
# Injects CORE's builtin modules into the SceneTree.
@ -228,13 +228,13 @@ func register_custom_module(module_name: String, module_origin: String, module_c
loggeri.error("Registering module failed: Custom module support is disabled.")
return false
if custom_modules.has(module_name):
loggeri.error("Registering module failed: A custom module with the name \"" + module_name + "\" already exists.")
loggeri.error(stringify_variables("Registering module failed: A custom module with the name %name% already exists.", { "name": module_name }))
return false
loggeri.diag("Updating variables")
module_class.name = module_name
module_class.core = self
module_class.loggeri = logger.get_instance(module_origin, module_class)
module_class.loggeri.framework = true
module_class.logger.framework = true
loggeri.diag("Adding module to SceneTree")
custom_modules_node.add_child(module_class)
loggeri.diag("Merging module with custom_modules")
@ -249,11 +249,11 @@ func register_custom_module(module_name: String, module_origin: String, module_c
func unregister_custom_module(module_name: String) -> void:
loggeri.verb("Unregistering custom module \"" + module_name + "\"")
if !custom_modules.has(module_name):
loggeri.error("Unregistering module failed: A custom module with the name \"" + module_name + "\" does not exist.")
loggeri.error(stringify_variables("Unregistering module failed: A custom module with the name %name% does not exist.", { "name": module_name }))
return
var module: Node = custom_modules[module_name]
await module._cleanup()
module.loggeri.queue_free()
module.logger.queue_free()
custom_modules_node.remove_child(module)
custom_modules.erase(module_name)
module.queue_free()
@ -263,7 +263,7 @@ func unregister_custom_module(module_name: String) -> void:
func get_custom_module(module_name: String) -> CoreBaseModule:
loggeri.diag("Getting custom module \"" + module_name + "\"")
if !custom_modules.has(module_name):
loggeri.error("Getting module failed: A custom module with the name \"" + module_name + "\" does not exist.")
loggeri.error(stringify_variables("Getting module failed: A custom module with the name %name% does not exist.", { "name": module_name }))
return null
return custom_modules[module_name]
@ -276,7 +276,7 @@ func cleanup() -> void:
loggeri.verb("Calling cleanup hooks")
for hook in cleanup_hooks:
if !cleanup_hooks[hook].is_valid():
loggeri.error("Cleanup hook #" + str(hook) + " is invalid")
loggeri.error(stringify_variables("Cleanup hook %id% is invalid", { "id": hook }))
else:
loggeri.diag("Calling cleanup hook #" + str(hook))
await cleanup_hooks[hook].call()
@ -302,10 +302,10 @@ func cleanup() -> void:
func _generate_hook_id() -> int:
var id = randi()
if cleanup_hooks.has(id):
loggeri.warn("New cleanup hook id #" + str(id) + " is already taken")
loggeri.warn(stringify_variables("New cleanup hook id %id% is already taken", { "id": id }))
return _generate_hook_id()
elif id == -1:
loggeri.warn("Invalid cleanup hook id '-1'")
loggeri.warn(stringify_variables("Invalid cleanup hook id %id%", { "id": id }))
return _generate_hook_id()
return id
@ -324,9 +324,9 @@ func register_cleanup_hook(callable: Callable) -> int:
## Unregisters a cleanup hook by it's id.
func unregister_cleanup_hook_by_id(id: int) -> bool:
if !cleanup_hooks.has(id):
loggeri.error("Could not remove cleanup hook (id): Hook #" + str(id) + " does not exist")
loggeri.error(stringify_variables("Could not remove cleanup hook (id): Hook %id% does not exist", { "id": id }))
return false
loggeri.verb("Removed cleanup hook #" + str(id) + " (id)")
loggeri.verb(stringify_variables("Removed cleanup hook %id%", { "id": id }))
cleanup_hooks.erase(id)
return true
@ -336,9 +336,9 @@ func unregister_cleanup_hook_by_ref(callable: Callable) -> bool:
if id == null:
loggeri.error("Could not remove cleanup hook (ref): Could not find a matching hook")
return false
if typeof(id) != TYPE_INT: await loggeri.crash("Could not remove cleanup hook (ref): find_key did not return an integer (returned '" + str(id) + "')")
loggeri.verb("Removed cleanup hook #" + str(id) + " (ref)")
if typeof(id) != TYPE_INT: await loggeri.crash(stringify_variables("Could not remove cleanup hook (ref): find_key did not return an integer (returned %id%)", { "id": id }))
cleanup_hooks.erase(id)
loggeri.verb(stringify_variables("Removed cleanup hook %id% (ref)", { "id": id }))
return true
# +++ etc +++
@ -455,8 +455,13 @@ func quit_safely(exitcode: int = 0) -> void:
await cleanup()
get_tree().quit(exitcode)
# Just ignore this.
## Internal, don't call
# Makes misc.stringify_variables() calls shorter
func stringify_variables(template: String, arguments: Dictionary, no_quotes: bool = false) -> String:
return misc.stringify_variables(template, arguments, no_quotes, true)
## Internal, don't call.
# Just ignore this.
func _notification(what) -> void:
match(what):
NOTIFICATION_WM_CLOSE_REQUEST:

View file

@ -54,11 +54,12 @@ func _cleanup() -> void:
## [/codeblock]
## [b]Note: [i]Using the [code]await[/code] keyword is required for this function.[/i][/b]
func awaited_request(url: String, parse_utf8: bool, method: HTTPClient.Method = HTTPClient.Method.METHOD_GET, headers: PackedStringArray = PackedStringArray([]), data: String = "") -> Dictionary:
loggeri.verb("Creating awaited request")
logger.verb("Creating awaited request")
if !await is_url_allowed(url): return {}
var id: int = create_request(url, method, headers, data)
start_request(id, parse_utf8)
loggeri.diag("Waiting for request " + str(id) + " to finish")
logger.diag("Waiting for request " + str(id) + " to finish")
logger.diag(core.stringify_variables("Waiting for request %id% to finish", { "id": id }))
while !is_request_completed(id): await get_tree().create_timer(0.1, true).timeout
var dldata: Dictionary = list_complete[id]
list_complete.erase(id)
@ -93,7 +94,7 @@ func oneline_awaited_request(url: String, return_utf8: bool = true, ignore_http_
## [/codeblock]
## [b]Note: [i]Using the [code]await[/code] keyword is required for this function.[/i][/b]
func batch_awaited_request(urls: PackedStringArray, parse_utf8: bool, method: HTTPClient.Method = HTTPClient.Method.METHOD_GET, headers: PackedStringArray = PackedStringArray([]), data: String = "") -> Array[Dictionary]:
loggeri.verb("Creating " + str(urls.size()) + " awaited request(s)")
logger.verb("Creating " + str(urls.size()) + " awaited request(s)")
var dldata: Array[Dictionary] = []
for url in urls:
if !await is_url_allowed(url): continue
@ -108,7 +109,7 @@ func batch_awaited_request(urls: PackedStringArray, parse_utf8: bool, method: HT
func _batch_awaited_request(url: String, parse_utf8: bool, method: HTTPClient.Method = HTTPClient.Method.METHOD_GET, headers: PackedStringArray = PackedStringArray([]), data: String = "") -> int:
var id: int = create_request(url, method, headers, data)
start_request(id, parse_utf8)
loggeri.diag("Waiting for request " + str(id) + " to finish")
logger.diag(core.stringify_variables("Waiting for request %id% to finish", { "id": id }))
while !is_request_completed(id): await get_tree().create_timer(0.1, true).timeout
return id
@ -117,15 +118,15 @@ func _batch_awaited_request(url: String, parse_utf8: bool, method: HTTPClient.Me
func generate_id() -> int:
var id = randi()
if list_queue.has(id) or list_active.has(id):
loggeri.warn("New download id '" + str(id) + "' is already taken")
logger.warn(core.stringify_variables("New download id %id% already taken", { "id": id }))
return generate_id()
loggeri.diag("Generated new download id " + str(id))
logger.diag(core.stringify_variables("Generated new download id %id%", { "id": id }))
return id
## Creates a new request and stores it in the queue. Returns the download id.[br]
## [b]Warning: [i]You'll probably not need this. Only use this function when implementing your own downloading method.[/i][/b]
func create_request(url: String, method: HTTPClient.Method = HTTPClient.Method.METHOD_GET, headers: PackedStringArray = PackedStringArray([]), body: String = "") -> int:
loggeri.verb("Creating new request\n-> URL: " + url + "\n-> Method: " + str(method) + "\nHeaders: " + str(headers.size()) + "\nBody size: " + str(body.length()) + " Characters")
logger.verb("Creating new request\n-> URL: " + url + "\n-> Method: " + str(method) + "\nHeaders: " + str(headers.size()) + "\nBody size: " + str(body.length()) + " Characters")
var id = generate_id()
list_queue.merge({ id: { "url": url, "method": method, "headers": headers, "body": body } })
return id
@ -134,16 +135,17 @@ func create_request(url: String, method: HTTPClient.Method = HTTPClient.Method.M
## [b]Note: [i]Using the [code]await[/code] keyword is required for this function.[/i][/b][br]
## [b]Warning: [i]You'll probably not need this. Only use this function when implementing your own downloading method.[/i][/b]
func start_request(id: int, parse_utf8: bool) -> void:
loggeri.verb("Starting request " + str(id))
logger.verb("Starting request " + str(id))
logger.verb(core.stringify_variables("Starting request %id%", { "id": id }))
list_active.merge({ id: list_queue[id] })
list_queue.erase(id)
loggeri.diag("Creating new HTTPRequest \"Request #" + str(id) + "\"")
logger.diag("Creating new HTTPRequest \"Request #" + str(id) + "\"")
var download: HTTPRequest = HTTPRequest.new()
download.name = "Request #" + str(id)
download.accept_gzip = true
download.use_threads = true
var lambda: Callable = func(result: int, http_code: int, headers: PackedStringArray, body: PackedByteArray, httprequest: HTTPRequest) -> void:
loggeri.verb("Request " + str(id) + " completed\nResult: " + str(result) + "\nHTTP response code: " + str(http_code) + "\nHeaders: " + str(headers.size()) + "\nBody size: " + str(body.size()) + " Bytes // " + str(core.misc.byte2mib(body.size(), true)) + " MiB\nParse body as UTF-8: " + str(parse_utf8))
logger.verb(core.stringify_variables("Request %id% completed\nResult: %result%\nHTTP response code: %http_code%\nHeaders: %headers%\nBody size: %body_size_bytes% Bytes // %body_size_mib% MiB\nParsed body as UTF-8: %parse_utf8%", { "result": result, "http_code": http_code, "headers": headers.size(), "body_size_bytes": body.size(), "body_size_mib": core.misc.byte2mib(body.size(), true), "parse_utf8": parse_utf8 }, true))
list_complete.merge({ id: { "result": result, "http_code": http_code, "headers": headers, "body": body, "body_utf8": body.get_string_from_utf8() if parse_utf8 else "" } })
list_active.erase(id)
remove_child(httprequest)
@ -157,14 +159,14 @@ func is_url_allowed(url: String) -> bool:
if url.begins_with("http://"):
match(config_unsecure_requests):
CoreTypes.BlockadeLevel.BLOCK:
loggeri.error("Blocked unsecure url '" + url + "'")
logger.error(core.stringify_variables("Blocked unsecure url %url%", { "url": url }))
return false
CoreTypes.BlockadeLevel.WARN: loggeri.warn("Requesting unsecure url '" + url + "'")
CoreTypes.BlockadeLevel.WARN: logger.warn(core.stringify_variables("Requesting unsecure url %url%", { "url": url }))
CoreTypes.BlockadeLevel.IGNORE: pass
_: await loggeri.crash("Invalid BlockadeLevel '" + str(config_unsecure_requests) + "'")
_: await logger.crash(core.stringify_variables("Invalid BlockadeLevel %level%", { "level": config_unsecure_requests }))
elif url.begins_with("https://"): pass
else:
loggeri.error("Invalid url '" + url + "'")
logger.error(core.stringify_variables("Invalid url %url%", { "url": url }))
return false
return true
@ -173,10 +175,10 @@ func is_request_completed(id: int) -> bool: return list_complete.has(id)
## Cleans the request queue.
func clean_queue() -> void:
loggeri.verb("Cleaning request queue")
logger.verb("Cleaning request queue")
list_queue.clear()
## Cleans the completed requests list.
func clean_completed() -> void:
loggeri.verb("Cleaning completed requests")
logger.verb("Cleaning completed requests")
list_complete.clear()

View file

@ -46,7 +46,7 @@ var config_newlines_sizelimit: int
func _cleanup() -> void:
for instance in instances:
if is_instance_valid(instance):
loggeri.diag("Removing instance '" + instance.name + "'")
logger.diag("Removing instance '" + instance.name + "'")
instance.queue_free()
func _schedule() -> void:
@ -54,7 +54,7 @@ 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 + "'")
logger.diag("Removing instance '" + instance.name + "'")
instance.queue_free()
instances_remove_enty.append(instance)
for instance in instances_remove_enty:

View file

@ -90,7 +90,7 @@ func _ready() -> void:
vsbar.add_theme_stylebox_override("grabber_pressed", StyleBoxEmpty.new())
# Connect log_event
logger.connect("log_event", func(allowed: bool, _level: CoreTypes.LoggerLevel, _origin: String, _message: String, format: String) -> void: if allowed: logrtl.text = logrtl.text + format + "\n")
core.logger.connect("log_event", func(allowed: bool, _level: CoreTypes.LoggerLevel, _origin: String, _message: String, format: String) -> void: if allowed: logrtl.text = logrtl.text + format + "\n")
# +++ process +++
func _process(_delta: float) -> void:

View file

@ -85,10 +85,10 @@ func format_stringarray(array: Array[String], item_before: String = "", item_aft
var output: String = ""
if array.size() == 0:
loggeri.warn("Unable to format a string with a size of 0")
logger.warn("Unable to format a string with a size of 0")
return ""
elif array.size() == 1:
loggeri.warn("Unable to format a string with a size of 1")
logger.warn("Unable to format a string with a size of 1")
return array[0]
for item in array:
@ -107,7 +107,7 @@ func array_to_stringarray(array: Array) -> Array[String]:
for item in array:
if typeof(item) != TYPE_STRING:
logger.error("Cannot convert Array to Array[String]: Item '" + str(item) + "' is not of type String")
logger.error(core.stringify_string("Cannot convert Array to Array[String]: Item %item% is not of type String", { "item": item }))
return []
output.append(item)
@ -151,7 +151,7 @@ func get_center(parent_size: Vector2, child_size: Vector2) -> Vector2:
## Output:
## [16:44:35] [DIAG Test.gd] Triggered '"shoot"' (pos='x=5156.149 y=581.69', successful='true')
## [/codeblock]
func stringify_variables(template: String, variables: Dictionary, no_quotes: bool = false) -> String:
func stringify_variables(template: String, variables: Dictionary, no_quotes: bool = false, force_no_type: bool = false) -> String:
# To decrease allocations
var value
var type: String = ""
@ -286,7 +286,7 @@ func stringify_variables(template: String, variables: Dictionary, no_quotes: boo
type = "unknown"
# Replace
if config_stringify_show_type:
if config_stringify_show_type and !force_no_type:
if type != "": type = "(" + type.to_lower() + ") "
else:
type = ""
@ -294,7 +294,7 @@ func stringify_variables(template: String, variables: Dictionary, no_quotes: boo
template = template.replace("%" + placeholder + "%", quote + type + replacement + quote)
return template
# Makes calls shorter
# Makes internal calls shorter
func _sa(value) -> String:
return stringify_variables("%var%", { "var": value }, true)

View file

@ -49,19 +49,19 @@ func _cleanup() -> void:
func _pull_config() -> void:
if core.config.headless:
# Remove all scenes
if is_inside_tree(): loggeri.verb("Removing all scenes (triggered by headless mode)")
if is_inside_tree(): logger.verb("Removing all scenes (triggered by headless mode)")
for scene in scenes: remove_scene(scene, true)
# +++ scene management +++
## Adds a scene to some scene collection.
func add_scene(scene_name: String, scene_class: Node, scene_type: CoreTypes.SceneType) -> bool:
if core.config.headless: return false
loggeri.verb("Adding scene \"" + scene_name + "\" of type " + str(scene_type))
logger.verb(core.stringify_variables("Adding scene %name% of type %type%", { "name": scene_name, "type": scene_type }))
if exists(scene_name) != CoreTypes.SceneType.NONE:
loggeri.error("Scene with name \"" + scene_name + "\" already exists")
logger.error(core.stringify_variables("A scene named %name% already exists", { "name": scene_name }))
return false
if typeof(scene_class) != TYPE_OBJECT or !scene_class.is_class("Node"):
loggeri.error("Scene class \"" + scene_name + "\" is not of type Node")
logger.error(core.stringify_variables("Scene class %name% is not of type Node", { "name": scene_name }))
return false
scene_class.name = scene_name
match(scene_type):
@ -71,9 +71,9 @@ func add_scene(scene_name: String, scene_class: Node, scene_type: CoreTypes.Scen
CoreTypes.SceneType.MAIN: scenes_main.add_child(scene_class)
CoreTypes.SceneType.BACKGROUND: scenes_background.add_child(scene_class)
CoreTypes.SceneType.NONE:
loggeri.error("CoreTypes.SceneType.NONE is not a valid scene type")
logger.error("CoreTypes.SceneType.NONE is not a valid scene type")
return false
_: await loggeri.crash("Invalid SceneType " + str(scene_type))
_: await logger.crash(core.stringify_variables("Invalid SceneType %type%", { "type": scene_type }))
scenes.merge({ scene_name: { "type": scene_type, "class": scene_class } })
return true
@ -81,8 +81,8 @@ func add_scene(scene_name: String, scene_class: Node, scene_type: CoreTypes.Scen
## [b]Danger: [i]Don't set [code]force_remove[/code] to [code]true[/code], thanks![/i][/b]
func remove_scene(scene_name: String, force_remove: bool = false) -> bool:
if core.config.headless and !force_remove: return false
if force_remove: await loggeri.crash("force_remove = true is not allowed")
loggeri.verb("Removing scene \"" + scene_name + "\"")
if force_remove: await logger.crash("force_remove is not allowed to be true")
logger.verb(core.stringify_variables("Removing scene %name%", { "name": scene_name }))
match(exists(scene_name)):
CoreTypes.SceneType.DEBUG:
scenes_debug.remove_child(scenes[scene_name]["class"])
@ -100,9 +100,9 @@ func remove_scene(scene_name: String, force_remove: bool = false) -> bool:
scenes_background.remove_child(scenes[scene_name]["class"])
scenes[scene_name]["class"].queue_free()
CoreTypes.SceneType.NONE:
loggeri.error("Scene \"" + scene_name + "\" does not exist")
logger.error(core.stringify_variables("Scene %name% does not exist", { "name": scene_name }))
return false
_: await loggeri.crash("Invalid SceneType " + str(exists(scene_name)))
_: await logger.crash(core.stringify_variables("Invalid SceneType %type%", { "type": exists(scene_name) }))
scenes.erase(scene_name)
return true
@ -118,8 +118,8 @@ func get_scene(scene_name: String) -> Node:
CoreTypes.SceneType.MENU: return scenes[scene_name]["class"]
CoreTypes.SceneType.MAIN: return scenes[scene_name]["class"]
CoreTypes.SceneType.BACKGROUND: return scenes[scene_name]["class"]
CoreTypes.SceneType.NONE: loggeri.error("Scene \"" + scene_name + "\" does not exist")
_: await loggeri.crash("Invalid SceneType " + str(exists(scene_name)))
CoreTypes.SceneType.NONE: logger.error(core.stringify_variables("Scene %name% does not exist", { "name": scene_name }))
_: await logger.crash(core.stringify_variables("Invalid SceneType %type%", { "type": exists(scene_name) }))
return null
## Returns a scene collection node.[br]
@ -133,8 +133,8 @@ func get_scene_collection(scene_type: CoreTypes.SceneType) -> Node:
CoreTypes.SceneType.MENU: return scenes_menu
CoreTypes.SceneType.MAIN: return scenes_main
CoreTypes.SceneType.BACKGROUND: return scenes_background
CoreTypes.SceneType.NONE: loggeri.error("No scene collection was found for CoreTypes.SceneType.NONE")
_: await loggeri.crash("Invalid SceneType " + str(scene_type))
CoreTypes.SceneType.NONE: logger.error("No scene collection was found for CoreTypes.SceneType.NONE")
_: await logger.crash(core.stringify_variables("Invalid SceneType %type%", { "type": scene_type }))
return null
## Returns a list of all loaded scenes in some scene collection.

View file

@ -32,40 +32,40 @@ var storage_location: String = ""
## Opens a storage file and loads it into memory.
func open_storage(location: String, create_new: bool = true, sanity_check: bool = true, fail_on_sanity_check: bool = false) -> bool:
if is_open:
loggeri.error("Failed to open storage: A storage file is already open")
logger.error("Failed to open storage: A storage file is already open")
return false
loggeri.verb("Opening storage file at \"" + location + "\"")
logger.verb(core.stringify_variables("Opening storage file at %location%", { "location": location }))
var file: FileAccess
if !FileAccess.file_exists(location):
if create_new:
file = FileAccess.open(location, FileAccess.WRITE)
if file == null:
await loggeri.crash("Could not open storage file at \"" + location + "\": Failed with code " + str(FileAccess.get_open_error()))
await logger.crash(core.stringify_variables("Could not open storage file at %location%: Failed with error %error%", { "location": location, "error": error_string(FileAccess.get_open_error()) }))
return false
file.store_string("{}")
file.close()
else:
loggeri.error("Failed to open storage: create_new is set to false")
logger.error("Failed to open storage: create_new is set to false")
return false
file = FileAccess.open(location, FileAccess.READ)
var storage_temp: Variant = file.get_as_text()
file.close()
storage_temp = JSON.parse_string(storage_temp)
if typeof(storage_temp) != TYPE_DICTIONARY:
loggeri.error("Failed to open storage: Parsed storage file is of type " + str(typeof(storage_temp)))
logger.error(core.stringify_variables("Failed to open storage: Parsed storage file is of type %type%", { "type": type_string(typeof(storage_temp)) }))
return false
if sanity_check:
var check_result: Array[String] = perform_sanity_check(storage_temp)
if check_result.size() != 0:
if fail_on_sanity_check:
loggeri.error("Sanity check failed (stopping):")
logger.error("Sanity check failed (stopping):")
for error in check_result:
loggeri.error("-> " + error)
logger.error("-> " + error)
return false
else:
loggeri.warn("Sanity check failed (continuing anyway):")
logger.warn("Sanity check failed (continuing anyway):")
for error in check_result:
loggeri.warn("-> " + error)
logger.warn("-> " + error)
storage = storage_temp
storage_location = location
is_open = true
@ -74,9 +74,9 @@ func open_storage(location: String, create_new: bool = true, sanity_check: bool
## Closes the active storage file.
func close_storage() -> bool:
if !is_open:
loggeri.error("Failed to close storage: No storage file is open")
logger.error("Failed to close storage: No storage file is open")
return false
loggeri.verb("Closing storage file")
logger.verb("Closing storage file")
storage = {}
is_open = false
return true
@ -84,13 +84,13 @@ func close_storage() -> bool:
## Saves the active storage file to disk.
func save_storage() -> bool:
if !is_open:
loggeri.error("Failed to save storage: No storage file is open")
logger.error("Failed to save storage: No storage file is open")
return false
var file: FileAccess = FileAccess.open(storage_location, FileAccess.WRITE)
if file == null:
await loggeri.crash("Could not open storage file at \"" + storage_location + "\": Failed with code " + str(FileAccess.get_open_error()))
await logger.crash(core.stringify_variables("Could not open storage file at %location%: Failed with error %error%", { "location": storage_location, "error": error_string(FileAccess.get_open_error()) }))
return false
loggeri.diag("Writing storage file to disk")
logger.diag("Writing storage file to disk")
file.store_string(JSON.stringify(storage))
file.close()
return true
@ -99,9 +99,9 @@ func save_storage() -> bool:
## Removes all keys from the active storage file. The nuclear option basically.
func nuke_storage(autosave: bool = true) -> bool:
if !is_open:
loggeri.error("Failed to nuke storage: No storage file is open")
logger.error("Failed to nuke storage: No storage file is open")
return false
loggeri.warn("Nuking storage")
logger.warn("Nuking storage")
storage = {}
if autosave: save_storage()
return true
@ -109,17 +109,17 @@ func nuke_storage(autosave: bool = true) -> bool:
## Returns a storage key. Can also return a default value if unset.
func get_key(key: String, default: Variant = null) -> Variant:
if !is_open:
loggeri.error("Failed to get key: No storage file is open")
logger.error("Failed to get key: No storage file is open")
return NAN
loggeri.diag("Returning storage key \"" + key + "\" (default='" + str(default) + "')")
logger.diag(core.stringify_variables("Returning storage key %key% (default=%default%)", { "key": key, "default": default }))
return storage.get(key, default)
## Updates a storage key with the specified value.
func set_key(key: String, value: Variant, overwrite: bool = true, autosave: bool = true) -> bool:
if !is_open:
loggeri.error("Failed to set key: No storage file is open")
logger.error("Failed to set key: No storage file is open")
return false
loggeri.diag("Updating storage key \"" + key + "\" with value '" + str(value) + "' (overwrite='" + str(overwrite) + "' autosave='" + str(autosave) + "'")
logger.diag(core.stringify_variables("Updating storage key %key% with value %value% (overwrite=%overwrite% autosave=%autosave%)", { "key": key, "value": value, "overwrite": overwrite, "autosave": autosave }))
storage.merge({key: value}, overwrite)
if autosave: save_storage()
return true
@ -129,7 +129,7 @@ func del_key(key: String, autosave: bool = true) -> bool:
if !is_open:
logger.errof("storage", "Failed to delete key: No storage file is open")
return false
loggeri.diag("Deleting storage key \"" + key + "\" (autosave='" + str(autosave) + "')")
logger.diag(core.stringify_variables("Deleting storage key %key% (autosave=%autosave%)", { "key": key, "autosave": autosave }))
storage.erase(key)
if autosave: save_storage()
return true
@ -139,30 +139,30 @@ func del_key(key: String, autosave: bool = true) -> bool:
## pass your modified [class Dictionary to [method safe_dict].
func get_dict() -> Dictionary:
if !is_open:
loggeri.error("Failed to get dictionary: No storage file is open")
logger.error("Failed to get dictionary: No storage file is open")
return {}
loggeri.verb("Returning storage dictionary")
logger.verb("Returning storage dictionary")
return storage
# +++ raw manipulation +++
## Saves a arbitrary dictionary as a [param storage] [class Dictionary] with sanity checking ([code]sanity_check[/code] and [code]fail_on_sanity_check[/code]).
func save_dict(dict: Dictionary, sanity_check: bool = true, fail_on_sanity_check: bool = false, autosave: bool = true) -> bool:
if !is_open:
loggeri.error("Failed to save dictionary: No storage file is open")
logger.error("Failed to save dictionary: No storage file is open")
return false
loggeri.verb("Saving custom dictionary as storage")
logger.verb("Saving custom dictionary as storage")
if sanity_check:
var check_result: Array[String] = perform_sanity_check(dict)
if check_result.size() != 0:
if fail_on_sanity_check:
loggeri.error("Sanity check failed (stopping):")
logger.error("Sanity check failed (stopping):")
for error in check_result:
loggeri.error("-> " + error)
logger.error("-> " + error)
return false
else:
loggeri.warn("Sanity check failed (continuing anyway):")
logger.warn("Sanity check failed (continuing anyway):")
for error in check_result:
loggeri.warn("-> " + error)
logger.warn("-> " + error)
storage = dict
if autosave: save_storage()
return true
@ -170,14 +170,14 @@ func save_dict(dict: Dictionary, sanity_check: bool = true, fail_on_sanity_check
# +++ etc +++
## Performs sanity checks on a [class Dictionary] to determine if it can be saved and loaded using this module.
func perform_sanity_check(storage_check: Dictionary) -> Array[String]:
loggeri.verb("Performing a sanity check on some storage dictionary")
logger.verb("Performing a sanity check on some storage dictionary")
var errors: Array[String] = []
for key in storage_check:
if typeof(key) != TYPE_STRING:
errors.append("Key \"" + str(key) + "\" is not of type String (type '" + type_string(typeof(key)) + "')")
errors.append(core.stringify_variables("Key %key% is not of type String (is of type %type%)", { "key": key, "type": type_string(typeof(key)) }))
continue
if typeof(storage_check[key]) != TYPE_NIL and typeof(storage_check[key]) != TYPE_STRING and typeof(storage_check[key]) != TYPE_INT and typeof(storage_check[key]) != TYPE_FLOAT and typeof(storage_check[key]) != TYPE_BOOL and typeof(storage_check[key]) != TYPE_ARRAY and typeof(storage_check[key]) != TYPE_DICTIONARY:
errors.append("The value of \"" + key + "\" is not null, a string, an integer, a float, boolean, array or dictionary (type '" + type_string(typeof(key)) + "')")
errors.append(core.stringify_variables("The value of %key% is not null, a String, an int, a float, boolean, an Array or a Dictionary (is of type %type%)", { "key": key, "type": type_string(typeof(key)) }))
loggeri.verb("Completed sanity check with " + str(errors.size()) + " errors")
logger.verb(core.stringify_variables("Completed sanity check with %errors% errors", { "errors": errors.size() }))
return errors