diff --git a/src/core.gd b/src/core.gd index 1eae33d..615ec0d 100644 --- a/src/core.gd +++ b/src/core.gd @@ -161,7 +161,7 @@ func get_formatted_string(string: String) -> String: CoreTypes.VersionType.ALPHA: string = string.replace("%type%", "Alpha") string = string.replace("%type_technical%", "a") - _: await logger.crash("Invalid version type " + str(version_type)) + _: await logger.crash("Invalid version type " + str(version_type), true) # Development mode if is_devmode(): string = string.replace("%devmode%", "Enabled") else: string = string.replace("%devmode%", "Disabled") diff --git a/src/logger.gd b/src/logger.gd index 350f33f..f05fe50 100644 --- a/src/logger.gd +++ b/src/logger.gd @@ -77,9 +77,9 @@ func _log(level: CoreTypes.LoggerLevel, message: String) -> void: format_newline = format_newline.replace("%level%", "ERR!") format = format.replace("%color%", "[color=red]") CoreTypes.LoggerLevel.NONE: - format = format.replace("%level%", "NONE") - format_newline = format_newline.replace("%level%", "NONE") - format = format.replace("%color%", "[color=black]") + format = format.replace("%level%", "CRSH") + format_newline = format_newline.replace("%level%", "CRSH") + format = format.replace("%color%", "[b][color=red]") # Replace %message% if config_newlines_override and config_newlines_sizelimit <= -1 or format_newline.length() <= config_newlines_sizelimit: message = message.replace("\n", "\n" + " ".repeat(format_newline.length())) format = format.replace("%message%", message) @@ -88,8 +88,8 @@ func _log(level: CoreTypes.LoggerLevel, message: String) -> void: else: print(format) # Get function caller -func get_origin() -> Dictionary: - var stack: Dictionary = get_stack()[3] +func get_origin(n: int = 1) -> Dictionary: + var stack: Dictionary = get_stack()[2+n] return { "source": stack["source"], "source_clean": stack["source"].replace("res://", "").replace("user://", ""), "function": stack["function"], "line": stack["line"] } # Check if level is allowed @@ -103,6 +103,98 @@ func verb(message: String) -> void: _log(CoreTypes.LoggerLevel.VERB, message) func info(message: String) -> void: _log(CoreTypes.LoggerLevel.INFO, message) func warn(message: String) -> void: _log(CoreTypes.LoggerLevel.WARN, message) func error(message: String) -> void: _log(CoreTypes.LoggerLevel.ERROR, message) -func crash(_message: String) -> void: - await get_tree().process_frame - error("crash() isn't implemented yet") + +# Built-in crash handler for CORE and applications using it +func crash(_message: String, framework_crash: bool = false) -> void: + # Collect information + var stack: Array[Dictionary] = get_stack() + var origin: Dictionary = get_origin(0) + var mem_info: Dictionary = OS.get_memory_info() + var crash_message: String = """Generating crash report... +###################################### +### CORE CRASH HANDLER ### +###################################### +%causer% inflicted a crash! +%script% (func %function%, line %line%) issues: +%message% + + +++ CORE INFORMATION +++ +VERSION + -> Release %version_release% + -> Type %version_type% + -> Typerelease %version_typerelease% + -> Full %version_full% + -> SemVer %version_semantic% +MODES + -> Development %devmode% + -> Headless %headless% + + +++ SYSTEM INFORMATION +++ +OPERATING SYSTEM + -> Operating System %os% + -> Version %os_version% + -> Distribution %os_distribution% +LOCALE + -> Locale %locale% + -> Language %locale_lang% +MEMORY + -> Free %mem_free% + -> Available %mem_avail% + -> Used (by engine) %mem_used% + -> Total (physical) %mem_total% + -> Peak %mem_peak% +PROCESSOR + -> Name %processor_name% + -> Count %processor_count% +VIDEO + -> Adapter Information %video_adapter% + + +++ GODOT ENGINE INFORMATION +++ +BUILD + -> Debug build %godot_debug% + -> Sandboxed %godot_sandboxed% +USERDATA + -> Persistent %godot_persistance% +STACKTRACE +%godot_stacktrace% +###################################### +### CORE CRASH HANDLER ### +######################################""" + # Replace placeholders + if framework_crash: crash_message = crash_message.replace("%causer%", "The CORE Framework") + else: crash_message = crash_message.replace("%causer%", "The running application") + crash_message = crash_message.replace("%script%", origin["source_clean"]) + crash_message = crash_message.replace("%function%", origin["function"]) + crash_message = crash_message.replace("%line%", str(origin["line"])) + crash_message = crash_message.replace("%message%", _message) + crash_message = crash_message.replace("%version_release%", str(core.version_release)) + crash_message = crash_message.replace("%version_type%", await core.get_formatted_string("%type%")) + crash_message = crash_message.replace("%version_typerelease%", str(core.version_typerelease)) + crash_message = crash_message.replace("%version_full%", str(core.version_release) + await core.get_formatted_string("-%type_technical%") + str(core.version_typerelease)) + crash_message = crash_message.replace("%version_semantic%", await core.get_formatted_string("%release_semantic%")) + crash_message = crash_message.replace("%devmode%", str(core.is_devmode())) + crash_message = crash_message.replace("%headless%", str(core.config.headless)) + crash_message = crash_message.replace("%os%", OS.get_name()) + crash_message = crash_message.replace("%os_version%", OS.get_version()) + crash_message = crash_message.replace("%os_distribution%", OS.get_distribution_name()) + crash_message = crash_message.replace("%locale%", OS.get_locale()) + crash_message = crash_message.replace("%locale_lang%", OS.get_locale_language()) + crash_message = crash_message.replace("%mem_free%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["free"], false))) + " GiB") + crash_message = crash_message.replace("%mem_avail%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["available"], false))) + " GiB") + crash_message = crash_message.replace("%mem_used%", str(core.misc.byte2mib(OS.get_static_memory_usage())) + " MiB") + crash_message = crash_message.replace("%mem_total%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["physical"], false))) + " GiB") + crash_message = crash_message.replace("%mem_peak%", str(core.misc.byte2mib(OS.get_static_memory_peak_usage())) + " MiB") + crash_message = crash_message.replace("%processor_name%", OS.get_processor_name()) + crash_message = crash_message.replace("%processor_count%", str(OS.get_processor_count())) + crash_message = crash_message.replace("%video_adapter%", str(OS.get_video_adapter_driver_info())) + crash_message = crash_message.replace("%godot_debug%", str(OS.is_debug_build())) + crash_message = crash_message.replace("%godot_sandboxed%", str(OS.is_sandboxed())) + crash_message = crash_message.replace("%godot_persistance%", str(OS.is_userfs_persistent())) + crash_message = crash_message.replace("%godot_stacktrace%", str(stack)) + # Enable newline overrides + config_newlines_override = true + config_newlines_sizelimit = -1 + # Print crash message + _log(CoreTypes.LoggerLevel.NONE, crash_message) + # Shutdown + await core.misc.quit_safely(69) diff --git a/src/misc.gd b/src/misc.gd index b1aa745..e5c4439 100644 --- a/src/misc.gd +++ b/src/misc.gd @@ -27,3 +27,17 @@ func quit_safely(exitcode: int = 0) -> void: logger.diag("Waiting for log messages to be flushed") await get_tree().create_timer(0.25).timeout get_tree().quit(exitcode) + +@warning_ignore("integer_division") +func byte2mib(bytes: int, flatten: bool = true) -> float: + if flatten: return bytes/1048576 + return bytes/float(1048576) + +func mib2byte(mib: int) -> int: return mib*1048576 + +@warning_ignore("integer_division") +func mib2gib(mib: float, flatten: bool = true) -> float: + if flatten: return int(mib)/1024 + return mib/float(1024) + +func gib2mib(gib: float) -> float: return gib*1024