# 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 . ## Contains various utility functions. ## ## This module contains many methods that don't fit into any other module ## and generally make your life as a developer easier. extends CoreBaseModule # Configuration var config_stringify_show_type: bool var config_stringify_color_range8: bool var config_stringify_array: bool var config_stringify_dictionary: bool # +++ module +++ func _pull_config() -> void: config_stringify_show_type = core.config.misc_stringify_show_type config_stringify_color_range8 = core.config.misc_stringify_color_range8 config_stringify_array = core.config.misc_stringify_array config_stringify_dictionary = core.config.misc_stringify_dictionary # +++ data type conversion +++ ## Converts a number of bytes into mebibytes.[br] ## [br] ## If [code]flatten[/code] is set to [code]true[/code], the decimal part will be discarded. @warning_ignore("integer_division") func byte2mib(bytes: int, flatten: bool = true) -> float: if flatten: return bytes/1048576 return bytes/float(1048576) ## Converts a number of mebibytes into bytes.[br] ## [br] ## If [code]flatten[/code] is set to [code]true[/code], the decimal part will be discarded. func mib2byte(mib: float, flatten: bool = true) -> float: if flatten: return int(mib*1048576) return mib*1048576 ## Converts a number of mebibytes into gibibytes.[br] ## [br] ## If [code]flatten[/code] is set to [code]true[/code], the decimal part will be discarded. func mib2gib(mib: float, flatten: bool = true) -> float: if flatten: return int(mib/1024) return mib/1024 ## Converts a number of gebibytes into mebibytes.[br] ## [br] ## If [code]flatten[/code] is set to [code]true[/code], the decimal part will be discarded. func gib2mib(gib: float, flatten: bool = true) -> float: if flatten: return int(gib*1024) return gib*1024 # +++ type formatting +++ ## Converts a string array into a normal, nicely formatted string.[br] ## [br] ## With [code]item_before[/code] and [code]item_after[/code] you can customize the lists apperance. Here's an example on how to format every item bold: ## [codeblock] ## extends Node ## ## var core: Core = get_node("/root/CORE") ## var misc: CoreBaseModule = core.misc ## var logger: CoreLoggerInstance = core.logger.get_instance("some/script.gd") ## ## func _ready() -> void: ## var array: Array[String] = ["Apples", "Bananas", "Oranges"] ## ## logger.info(misc.format_stringarray(array)) ## logger.info(misc.format_stringarray(array, "[b]", "[/b]")) ## [/codeblock] func format_stringarray(array: Array[String], item_before: String = "", item_after: String = "", separator_list: String = ", ", separator_final: String = " & ") -> String: var output: String = "" if array.size() == 0: loggeri.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") return array[0] for item in array: if output == "": output = item_before + item + item_after else: output = output.replace("If you somehow see this text report this at https://git.staropensource.de/StarOpenSource/CORE/issues, thank you!", separator_list) + "If you somehow see this text report this at https://git.staropensource.de/StarOpenSource/CORE/issues, thank you!" + item_before + item + item_after output = output.replace("If you somehow see this text report this at https://git.staropensource.de/StarOpenSource/CORE/issues, thank you!", separator_final) return output # +++ array conversion +++ ## Converts an array into a string array.[br] ## [br] ## If an item is found that is not of type [code]String[/code], an empty array is returned. func array_to_stringarray(array: Array) -> Array[String]: var output: 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") return [] output.append(item) return output ## Converts a string array into an array. func stringarray_to_array(array: Array[String]) -> Array: var output: Array = [] for item in array: output.append(item) return output # +++ etc +++ ## Calculates the center of the child in the area of the parent.[br] ## [br] ## Example: ## [codeblock] ## extends Control ## ## var core: Core = get_node("/root/CORE") ## var misc: CoreBaseModule = core.misc ## ## func _ready() -> void: ## position = misc.center_object(get_parent().size, size) ## [/codeblock] func get_center(parent_size: Vector2, child_size: Vector2) -> Vector2: return Vector2(parent_size.x/2-child_size.x/2, parent_size.y/2-child_size.y/2) ## Makes variables as look correct inside strings.[br] ## Short examples:[br] ## [code]true[/code] -> [code]'true'[/code][br] ## [code]Vector2(69.064, PI)[/code] -> [code]'x=69.064 y=3.14159265358979'[/code][br] ## [code]"This is a test string"[/code] -> [code]'"This is a test string"'[/code][br] ## Full example:[br] ## [codeblock] ## Code: ## logger.diag(stringify_variables("Triggered %trigger% (pos=%position%, successful=%success%)", { "trigger": "shoot", "position": Vector2(5156.149, 581.69), "success": true })) ## ## 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: # To decrease allocations var value var type: String = "" var replacement: String = "" for placeholder in variables: # Check key type if typeof(placeholder) != TYPE_STRING: logger.error("Invalid placeholder type '\"" + type_string(typeof(placeholder)) + "\", skipping") continue # Check for correct type value = variables[placeholder] match(typeof(value)): # Primitives Variant.Type.TYPE_NIL: replacement = "null" type = "" Variant.Type.TYPE_BOOL: replacement = str(value) type = "bool" Variant.Type.TYPE_INT: replacement = str(value) type = "int" Variant.Type.TYPE_FLOAT: replacement = str(value) type = "float" Variant.Type.TYPE_STRING: replacement = "\"" + value + "\"" type = "String" Variant.Type.TYPE_STRING_NAME: replacement = "\"" + value + "\"" type = "StringName" # Non-primitives Variant.Type.TYPE_OBJECT: replacement = str(value) type = "Object" Variant.Type.TYPE_COLOR: if config_stringify_color_range8: replacement = "r=" + _sa(value.r8) + " g=" + _sa(value.g8) + " b=" + _sa(value.b8) + " a=" + _sa(value.a8) else: replacement = "r=" + _sa(value.r) + " g=" + _sa(value.g) + " b=" + _sa(value.b) + " a=" + _sa(value.a) type = "Color" Variant.Type.TYPE_RID: replacement = "id=" + _sa(value.get_id()) + " valid=" + _sa(value.is_valid()) type = "RID" Variant.Type.TYPE_ARRAY: if config_stringify_array: if value.size() == 0: replacement = "[]" else: replacement = "[ " for item in value: if replacement == "[ ": replacement += _sa(item) else: replacement += ", " + _sa(item) replacement += " ]" else: replacement = str(value) if value.get_typed_builtin() != TYPE_NIL: type = "Array[" + type_string(typeof(value.get_typed_builtin())) + "]" else: type = "Array" Variant.Type.TYPE_DICTIONARY: if config_stringify_dictionary: if value.size() == 0: replacement = "{}" else: replacement = "{ " for key in value: if replacement == "{ ": replacement += _sa(key) + ": " + _sa(value[key]) else: replacement += ", " + _sa(key) replacement += " }" else: replacement = str(value) type = "Dictionary" # TODO: Packed Arrays # Nodes & scripting Variant.Type.TYPE_NODE_PATH: replacement = str(value) type = "NodePath" Variant.Type.TYPE_CALLABLE: replacement = "valid=" + _sa(value.is_valid()) + " standard=" + _sa(value.is_standard()) + " object=" + _sa(value.get_object() ) + " method=" + value.get_method() + " args=" + _sa(value.get_bound_arguments()) type = "Callable" Variant.Type.TYPE_SIGNAL: replacement = "name=" + _sa(value.get_name()) + " object=" + _sa(value.get_object()) type = "Signal" # 2D Variant.Type.TYPE_VECTOR2: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) type = "Vector2" Variant.Type.TYPE_VECTOR2I: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) type = "Vector2i" Variant.Type.TYPE_RECT2: replacement = "size=" + _sa(value.size) + " pos=" + _sa(value.position) + " end=" + _sa(value.end) type = "Rect2" Variant.Type.TYPE_RECT2I: replacement = "size=" + _sa(value.size) + " pos=" + _sa(value.position) + " end=" + _sa(value.end) type = "Rect2i" Variant.Type.TYPE_TRANSFORM2D: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " origin=" + _sa(value.origin) type = "Transform2D" # 3D Variant.Type.TYPE_VECTOR3: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) type = "Vector3" Variant.Type.TYPE_VECTOR3I: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) type = "Vector3i" Variant.Type.TYPE_PLANE: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) + " d=" + _sa(value.d) + " normal=" + _sa(value.normal) type = "Plane" Variant.Type.TYPE_QUATERNION: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) + " w=" + _sa(value.w) type = "Quaternion" Variant.Type.TYPE_AABB: replacement = "size=" + _sa(value.size) + " pos=" + _sa(value.position) + " end=" + _sa(value.end) type = "AABB" Variant.Type.TYPE_TRANSFORM3D: replacement = "basis=" + _sa(value.basis) + " origin=" + _sa(value.origin) type = "Transform3D" Variant.Type.TYPE_BASIS: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) type = "Basis" Variant.Type.TYPE_PROJECTION: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) + " w=" + _sa(value.w) type = "Projection" # 4D Variant.Type.TYPE_VECTOR4: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) + " w=" + _sa(value.w) type = "Vector4" Variant.Type.TYPE_VECTOR4I: replacement = "x=" + _sa(value.x) + " y=" + _sa(value.y) + " z=" + _sa(value.z) + " w=" + _sa(value.w) type = "Vector4i" _: replacement = str(value) type = "unknown" # Replace if config_stringify_show_type: if type != "": type = "(" + type.to_lower() + ") " else: type = "" var quote: String = "'" if !no_quotes else "" template = template.replace("%" + placeholder + "%", quote + type + replacement + quote) return template # Makes calls shorter func _sa(value) -> String: return stringify_variables("%var%", { "var": value }, true) ## Moved to [method Core.quit_safely]. ## @deprecated func quit_safely(exitcode: int = 0) -> void: await core.quit_safely(exitcode)