diff --git a/.gitignore b/.gitignore index b2fe731ea..34a0c5590 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_STORE .haxelib/ +.vs/ APIStuff.hx dump/ export/ diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0ad3c19b8..0a59fb70b 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -202,8 +202,9 @@ class InitState extends FlxState // // Plugins provide a useful interface for globally active Flixel objects, // that receive update events regardless of the current state. - // TODO: Move Module behavior to a Flixel plugin. + // TODO: Move scripted Module behavior to a Flixel plugin. funkin.util.plugins.EvacuateDebugPlugin.initialize(); + funkin.util.plugins.ForceCrashPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); funkin.util.plugins.ScreenshotPlugin.initialize(); funkin.util.plugins.VolumePlugin.initialize(); diff --git a/source/funkin/ui/debug/MemoryCounter.hx b/source/funkin/ui/debug/MemoryCounter.hx index 312d853e7..b25b55645 100644 --- a/source/funkin/ui/debug/MemoryCounter.hx +++ b/source/funkin/ui/debug/MemoryCounter.hx @@ -1,5 +1,6 @@ package funkin.ui.debug; +import funkin.util.MemoryUtil; import openfl.text.TextFormat; import openfl.system.System; import openfl.text.TextField; @@ -35,7 +36,7 @@ class MemoryCounter extends TextField @:noCompletion #if !flash override #end function __enterFrame(deltaTime:Float):Void { - var mem:Float = Math.round(System.totalMemory / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; + var mem:Float = Math.round(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO; if (mem > memPeak) memPeak = mem; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a00c1681b..75352c21d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5258,14 +5258,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // F1 = Open Help if (FlxG.keys.justPressed.F1) this.openUserGuideDialog(); - - // DEBUG KEYBIND: Ctrl + Alt + Shift + L = Crash the game. - #if debug - if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L) - { - throw "DEBUG: Crashing the chart editor!"; - } - #end } function handleQuickWatch():Void diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx new file mode 100644 index 000000000..6b5f7deea --- /dev/null +++ b/source/funkin/util/MemoryUtil.hx @@ -0,0 +1,113 @@ +package funkin.util; + +/** + * Utilities for working with the garbage collector. + * + * HXCPP is built on Immix. + * HTML5 builds use the browser's built-in mark-and-sweep and JS has no APIs to interact with it. + * @see https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/immix/ + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management + * @see https://betterprogramming.pub/deep-dive-into-garbage-collection-in-javascript-6881610239a + * @see https://github.com/HaxeFoundation/hxcpp/blob/master/docs/build_xml/Defines.md + * @see cpp.vm.Gc + */ +class MemoryUtil +{ + public static function buildGCInfo():String + { + #if cpp + var result = "HXCPP-Immix:"; + result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; + result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; + result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; + result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_LARGE)} bytes'; + result += '\n- HXCPP Debugger: ${#if HXCPP_DEBUGGER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Generational Mode: ${#if HXCPP_GC_GENERATIONAL 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_MOVING 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_DYNAMIC_SIZE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_BIG_BLOCKS 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Debug Link: ${#if HXCPP_DEBUG_LINK 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace: ${#if HXCPP_STACK_TRACE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Stack Trace Line Numbers: ${#if HXCPP_STACK_LINE 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Pointer Validation: ${#if HXCPP_CHECK_POINTER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Profiler: ${#if HXCPP_PROFILER 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP Local Telemetry: ${#if HXCPP_TELEMETRY 'Enabled' #else 'Disabled' #end}'; + result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}'; + result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; + #elseif js + var result = "JS-MNS:"; + result += '\n- Memory Used: ${getMemoryUsed()} bytes'; + #else + var result = "Unknown GC"; + #end + + return result; + } + + /** + * Calculate the total memory usage of the program, in bytes. + * @return Int + */ + public static function getMemoryUsed():Int + { + #if cpp + // There is also Gc.MEM_INFO_RESERVED, MEM_INFO_CURRENT, and MEM_INFO_LARGE. + return cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE); + #else + return openfl.system.System.totalMemory; + #end + } + + /** + * Enable garbage collection if it was previously disabled. + */ + public static function enable():Void + { + #if cpp + cpp.vm.Gc.enable(true); + #else + throw "Not implemented!"; + #end + } + + /** + * Disable garbage collection entirely. + */ + public static function disable():Void + { + #if cpp + cpp.vm.Gc.enable(false); + #else + throw "Not implemented!"; + #end + } + + /** + * Manually perform garbage collection once. + * Should only be called from the main thread. + * @param major `true` to perform major collection, whatever that means. + */ + public static function collect(major:Bool = false):Void + { + #if cpp + cpp.vm.Gc.run(major); + #else + throw "Not implemented!"; + #end + } + + /** + * Perform major garbage collection repeatedly until less than 16kb of memory is freed in one operation. + * Should only be called from the main thread. + * + * NOTE: This is DIFFERENT from actual compaction, + */ + public static function compact():Void + { + #if cpp + cpp.vm.Gc.compact(); + #else + throw "Not implemented!"; + #end + } +} diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx index ad5983e52..ef293486d 100644 --- a/source/funkin/util/logging/CrashHandler.hx +++ b/source/funkin/util/logging/CrashHandler.hx @@ -125,6 +125,24 @@ class CrashHandler fullContents += '=====================\n'; + fullContents += '\n'; + + fullContents += MemoryUtil.buildGCInfo(); + + fullContents += '\n\n'; + + fullContents += '=====================\n'; + + fullContents += '\n'; + + fullContents += 'Flixel Current State: ${Type.getClassName(Type.getClass(FlxG.state))}\n'; + + fullContents += '\n'; + + fullContents += '=====================\n'; + + fullContents += '\n'; + fullContents += 'Haxelibs: \n'; for (lib in Constants.LIBRARY_VERSIONS) diff --git a/source/funkin/util/plugins/ForceCrashPlugin.hx b/source/funkin/util/plugins/ForceCrashPlugin.hx new file mode 100644 index 000000000..e8094eb3c --- /dev/null +++ b/source/funkin/util/plugins/ForceCrashPlugin.hx @@ -0,0 +1,37 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which forcibly crashes the application. + * TODO: Should we disable this in release builds? + */ +class ForceCrashPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ForceCrashPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + // Ctrl + Shift + L = Crash the game for debugging purposes + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.pressed.L) + { + // TODO: Make this message 87% funnier. + throw "DEBUG: Crashing the game via debug keybind!"; + } + } + + public override function destroy():Void + { + super.destroy(); + } +}