From dd159b064d9cd63a0255773de7a370d383309f2c Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 14 Feb 2026 01:25:04 -0500 Subject: [PATCH] Lots of refactors related to BreakToDebugger and FormatLogMessage --- Config/DefaultEngine.ini | 3 + Source/Integration/BlueprintErrors.h | 117 ------------------ ...lueprintErrors.cpp => BreakToDebugger.cpp} | 28 ++--- Source/Integration/BreakToDebugger.h | 113 +++++++++++++++++ Source/Integration/ConsoleOutput.h | 40 +++--- Source/Integration/FormatMessage.cpp | 22 +++- Source/Integration/FormatMessage.h | 50 +++++++- Source/Integration/LuaCallNode.h | 2 - Source/Integration/LuprexGameModeBase.cpp | 2 +- Source/Integration/LuprexGameModeBase.h | 6 +- 10 files changed, 221 insertions(+), 162 deletions(-) delete mode 100644 Source/Integration/BlueprintErrors.h rename Source/Integration/{BlueprintErrors.cpp => BreakToDebugger.cpp} (54%) create mode 100644 Source/Integration/BreakToDebugger.h diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index dad07245..702005ea 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -89,6 +89,9 @@ UIScaleCurve=(EditorCurveData=(Keys=((Time=480.000000,Value=0.444000),(Time=720. +CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") +CollisionChannelRedirects=(OldName="Clickable",NewName="LookAtDetection") +[CoreRedirects] ++EnumRedirects=(OldName="/Script/Integration.ElxLogVerbosity",NewName="/Script/Integration.ElxFormatLogVerbosity") + [/Script/GameplayDebugger.GameplayDebuggerConfig] ActivationKey=None diff --git a/Source/Integration/BlueprintErrors.h b/Source/Integration/BlueprintErrors.h deleted file mode 100644 index c13f3849..00000000 --- a/Source/Integration/BlueprintErrors.h +++ /dev/null @@ -1,117 +0,0 @@ -// -// BlueprintErrors: Better error handling for blueprint errors. -// - -#pragma once - -#include "Containers/Array.h" -#include "CoreMinimal.h" -#include "HAL/Platform.h" -#include "Misc/OutputDeviceError.h" -#include "UObject/NameTypes.h" -#include "UObject/ObjectMacros.h" -#include "UObject/UObjectGlobals.h" -#include "Kismet/BlueprintFunctionLibrary.h" - -#include "BlueprintErrors.generated.h" - -/* - * enum class ElxLogVerbosity, below, contains all the same error severity levels - * as ELogVerbosity, but in a form that the blueprint editor can manipulate. - * - * We deliberately moved 'Fatal' to the end of the list, and made 'Error' option 0. - * We did that because we want the editor to default to 'Error' in most cases. - * Unfortunately, that means the numeric values of the two enums don't match up, - * so we will need a conversion function. - * - * ThrottledDisplay and ThrottledLog are not present in ELogVerbosity. They - * behave like Display and Log respectively, but suppress repeated messages - * with the same format pattern, logging at most once per second. - * - */ - - -/** Log Verbosity: The importance of an error message, which affects which logs the error - * message gets written to, and how that message gets filtered. - * - */ -UENUM(BlueprintType) -enum class ElxLogVerbosity : uint8 { - - /* Prints an error to the console and log file. The editor collects and reports errors. */ - Error, - - /* Prints a warning to the console and log file. The editor collects and report warnings. */ - Warning, - - /* Prints a message to the console and log file. */ - Display, - - /* Prints a message to the log file, however, it does not print to the console. */ - Log, - - /* Like Display, but suppresses repeated messages with the same format pattern (at most once per second). */ - ThrottledDisplay, - - /* Like Log, but suppresses repeated messages with the same format pattern (at most once per second). */ - ThrottledLog, - - /* Prints a message to a log file only if Verbose logging is enabled for the given category. This is usually used for detailed logging. */ - Verbose, - - /* Prints a message to a log file. If VeryVerbose logging is enabled, then this is used for detailed logging that would otherwise spam output. */ - VeryVerbose, - - /* Danger! Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */ - Fatal, -}; - -/* A library containing assorted useful functions for blueprint error handling. - * - */ -UCLASS(MinimalAPI) -class UlxBlueprintErrorLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -public: - // Convert an ElxLogVerbosity to an ELogVerbosity::Type - // - static ELogVerbosity::Type ConvertElxLogVerbosity(ElxLogVerbosity Verbosity); -}; - -/* Debug Blueprint Errors output device. - * - * When an error message gets written to the log, using "Format Error Message," - * or any other means that writes an error message to the log, - * we can optionally notify the blueprint debugger to pause execution. - * This only affects errors that are generated during blueprint execution. - * Errors in other threads do not pause the blueprint. - * - */ -struct FlxDebugBlueprintErrorsOutputDevice : public FOutputDevice -{ -public: - // The constructor and destructor automatically register this output device with GLog. - // - // This struct doesn't store the sensitivity threshold. It relies on some blueprint - // class to do that, so that the threshold can be easily edited with the blueprint - // editor. This struct must be initialized with a reference to the threshold variable. - // - FlxDebugBlueprintErrorsOutputDevice(const ElxLogVerbosity &SensitivityRef); - ~FlxDebugBlueprintErrorsOutputDevice(); - - // Inspect a log message. - // - INTEGRATION_API virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) override; - - // If the device is marked 'CanBeUsedOnMultipleThreads,' then UE_LOG will - // call Serialize from the current thread, otherwise, it will call Serialize from - // the logging thread. Using the logging thread would defeat the purpose of this - // device, so it's imperative that we set this flag. - // - INTEGRATION_API virtual bool CanBeUsedOnMultipleThreads() const override { return true; } - -private: - const ElxLogVerbosity &Sensitivity; -}; diff --git a/Source/Integration/BlueprintErrors.cpp b/Source/Integration/BreakToDebugger.cpp similarity index 54% rename from Source/Integration/BlueprintErrors.cpp rename to Source/Integration/BreakToDebugger.cpp index e4b32ff1..a40a4fc2 100644 --- a/Source/Integration/BlueprintErrors.cpp +++ b/Source/Integration/BreakToDebugger.cpp @@ -1,29 +1,27 @@ -#include "BlueprintErrors.h" +#include "BreakToDebugger.h" #include "Blueprint/BlueprintExceptionInfo.h" #include "Kismet2/KismetDebugUtilities.h" -ELogVerbosity::Type UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(ElxLogVerbosity Verbosity) { +ELogVerbosity::Type FlxBreakToDebuggerOutputDevice::ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity) { switch (Verbosity) { - case ElxLogVerbosity::Error: return ELogVerbosity::Error; - case ElxLogVerbosity::Warning: return ELogVerbosity::Warning; - case ElxLogVerbosity::Display: return ELogVerbosity::Display; - case ElxLogVerbosity::Log: return ELogVerbosity::Log; - case ElxLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display; - case ElxLogVerbosity::ThrottledLog: return ELogVerbosity::Log; - case ElxLogVerbosity::Verbose: return ELogVerbosity::Verbose; - case ElxLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose; - case ElxLogVerbosity::Fatal: return ELogVerbosity::Fatal; + case ElxBreakToDebuggerThreshold::Error: return ELogVerbosity::Error; + case ElxBreakToDebuggerThreshold::Warning: return ELogVerbosity::Warning; + case ElxBreakToDebuggerThreshold::Display: return ELogVerbosity::Display; + case ElxBreakToDebuggerThreshold::Log: return ELogVerbosity::Log; + case ElxBreakToDebuggerThreshold::Verbose: return ELogVerbosity::Verbose; + case ElxBreakToDebuggerThreshold::VeryVerbose: return ELogVerbosity::VeryVerbose; + case ElxBreakToDebuggerThreshold::Fatal: return ELogVerbosity::Fatal; } } -FlxDebugBlueprintErrorsOutputDevice::FlxDebugBlueprintErrorsOutputDevice(const ElxLogVerbosity &SensitivityRef) +FlxBreakToDebuggerOutputDevice::FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef) : Sensitivity(SensitivityRef) { GLog->AddOutputDevice(this); } -FlxDebugBlueprintErrorsOutputDevice::~FlxDebugBlueprintErrorsOutputDevice() +FlxBreakToDebuggerOutputDevice::~FlxBreakToDebuggerOutputDevice() { GLog->RemoveOutputDevice(this); } @@ -46,11 +44,11 @@ namespace UBreakPoint { static const FName LogBlueprintDebugName(TEXT("LogBlueprintDebug")); -void FlxDebugBlueprintErrorsOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) +void FlxBreakToDebuggerOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) { // If the error isn't serious enough, do nothing. // - if (Verbosity > UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(Sensitivity)) + if (Verbosity > ConvertThreshold(Sensitivity)) { return; } diff --git a/Source/Integration/BreakToDebugger.h b/Source/Integration/BreakToDebugger.h new file mode 100644 index 00000000..5ab85aa8 --- /dev/null +++ b/Source/Integration/BreakToDebugger.h @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////// +// +// BreakToDebugger +// +// When an error message gets written to UE_LOG, we can +// optionally trigger the blueprint debugger. +// +// This only affects UE_LOG messages that are generated +// during blueprint execution. Log messages from other +// threads do not trigger the debugger. +// +// The following explains how we trigger the blueprint +// debugger on UE_LOG messages. Log messages are sent to a +// long list of output devices, including: the visual studio +// output window, the unreal editor output window, the log +// file, and so forth. We add another pseudo output device. +// This output device doesn't actually send the log message +// anywhere, instead, it just activates the blueprint +// debugger. +// +// UE_LOG messages can be generated from any thread. The +// pseudo output device checks what thread it is running in, +// and if it's not the blueprint thread, it does nothing at +// all. If it is the blueprint thread, it's safe to trigger +// the blueprint debugger. +// +// One annoying limitation of this design is that our output +// device ends up early in the list, so the debugger runs +// *before* the message shows up in visual studio or the +// unreal editor. As a result, when you are in the +// debugger, the message won't be in your output window. +// Pressing 'single step' always reveals the message. +// +// The blueprint node "Format Log Message" uses UE_LOG +// internally, so therefore, that too can trigger the +// blueprint debugger. +// +//////////////////////////////////////////////////////////// + +#pragma once + +#include "Containers/Array.h" +#include "CoreMinimal.h" +#include "HAL/Platform.h" +#include "Misc/OutputDeviceError.h" +#include "UObject/NameTypes.h" +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "BreakToDebugger.generated.h" + +// ElxBreakToDebuggerThreshold: +// +// Controls the sensitivity level at which UE_LOG messages +// trigger the blueprint debugger. +// +UENUM(BlueprintType) +enum class ElxBreakToDebuggerThreshold : uint8 { + + /* Break on errors. */ + Error, + + /* Break on warnings and above. */ + Warning, + + /* Break on display messages and above. */ + Display, + + /* Break on log messages and above. */ + Log, + + /* Break on verbose messages and above. */ + Verbose, + + /* Break on all messages. */ + VeryVerbose, + + /* Break on fatal errors only (ie, never break -- the process crashes instead). */ + Fatal, +}; + + +struct FlxBreakToDebuggerOutputDevice : public FOutputDevice +{ +public: + // The constructor and destructor automatically register + // this output device with GLog. + // + // This struct doesn't store the sensitivity threshold. + // It relies on the LuprexGameMode class to do that, so + // that the threshold can be easily edited with the + // blueprint editor. This struct must be initialized + // with a reference to the threshold variable. + // + FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef); + ~FlxBreakToDebuggerOutputDevice(); + + // Inspect a log message. + // + INTEGRATION_API virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) override; + + // If the device is marked 'CanBeUsedOnMultipleThreads,' + // then UE_LOG will call Serialize from the current + // thread, otherwise, it will call Serialize from the + // logging thread. Using the logging thread would + // defeat the purpose of this device, so it's imperative + // that we set this flag. + // + INTEGRATION_API virtual bool CanBeUsedOnMultipleThreads() const override { return true; } + +private: + static ELogVerbosity::Type ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity); + const ElxBreakToDebuggerThreshold &Sensitivity; +}; diff --git a/Source/Integration/ConsoleOutput.h b/Source/Integration/ConsoleOutput.h index 8a7cdf61..84925640 100644 --- a/Source/Integration/ConsoleOutput.h +++ b/Source/Integration/ConsoleOutput.h @@ -1,3 +1,26 @@ +////////////////////////////////////////////////////////////// +// +// ConsoleOutput +// +// This class can optionally be used by the GameMode +// blueprint to help implement the console window. There is +// no requirement that the blueprint use this class, it can +// implement the console window any way it wants, this is +// just here in case it is desired. +// +// This class stores the text that's in the unreal console. +// It stores it as one great big string, which contains +// newlines to denote line breaks. +// +// This class also contains a 'dirty' bit. Each time +// somebody appends a line of text to the console, the dirty +// bit is automatically set. The bit can be checked using +// 'IsDirty' and cleared using 'ClearDirty'. This makes it +// so that you don't have to update the unreal widget unless +// the text has actually changed. +// +////////////////////////////////////////////////////////////// + #pragma once #include "Containers/UnrealString.h" @@ -5,23 +28,6 @@ #include "ConsoleOutput.generated.h" -////////////////////////////////////////////////////////////// -// -// ConsoleOutput -// -// This class stores the text that's in the unreal console. -// It stores it as one great big string, which contains -// newlines to denote line breaks. -// -// This class also contains a 'dirty' bit. Each time somebody -// appends a line of text to the console, the dirty bit is -// automatically set. The bit can be checked using 'IsDirty' -// and cleared using 'ClearDirty'. This makes it so that -// you don't have to update the unreal widget unless the -// text has actually changed. -// -////////////////////////////////////////////////////////////// - UCLASS(BlueprintType) class UlxConsoleOutput : public UObject { diff --git a/Source/Integration/FormatMessage.cpp b/Source/Integration/FormatMessage.cpp index 52d1d151..e1ea0d0a 100644 --- a/Source/Integration/FormatMessage.cpp +++ b/Source/Integration/FormatMessage.cpp @@ -108,7 +108,7 @@ void UK2Node_FormatMessage::CreateCorrectPins() if (IsFormatErrorMessage()) { if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) { - UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), VerbosityPinName); + UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), VerbosityPinName); P->DefaultValue = TEXT("Error"); P->AutogeneratedDefaultValue = P->DefaultValue; } @@ -573,13 +573,27 @@ UK2Node_FormatLogMessage::UK2Node_FormatLogMessage(const FObjectInitializer& Obj ); } -void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxLogVerbosity Verbosity, const FString &InPattern, TArray InArgs) +ELogVerbosity::Type UK2Node_FormatMessage::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) { + switch (Verbosity) { + case ElxFormatLogVerbosity::Error: return ELogVerbosity::Error; + case ElxFormatLogVerbosity::Warning: return ELogVerbosity::Warning; + case ElxFormatLogVerbosity::Display: return ELogVerbosity::Display; + case ElxFormatLogVerbosity::Log: return ELogVerbosity::Log; + case ElxFormatLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display; + case ElxFormatLogVerbosity::ThrottledLog: return ELogVerbosity::Log; + case ElxFormatLogVerbosity::Verbose: return ELogVerbosity::Verbose; + case ElxFormatLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose; + case ElxFormatLogVerbosity::Fatal: return ELogVerbosity::Fatal; + } +} + +void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray InArgs) { // For throttled verbosity levels, suppress repeated messages with the // same format pattern. We key on the blueprint name + format pattern, // and allow at most one message per second per key. // - if (Verbosity == ElxLogVerbosity::ThrottledDisplay || Verbosity == ElxLogVerbosity::ThrottledLog) + if (Verbosity == ElxFormatLogVerbosity::ThrottledDisplay || Verbosity == ElxFormatLogVerbosity::ThrottledLog) { static TMap LastLogTime; double Now = FPlatformTime::Seconds(); @@ -618,7 +632,7 @@ void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxLogVer // Output to Log // - ELogVerbosity::Type VerbosityValue = UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(Verbosity); + ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity); if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY) { FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString); diff --git a/Source/Integration/FormatMessage.h b/Source/Integration/FormatMessage.h index e32ff845..992967f1 100644 --- a/Source/Integration/FormatMessage.h +++ b/Source/Integration/FormatMessage.h @@ -2,7 +2,7 @@ #pragma once -#include "BlueprintErrors.h" +#include "BreakToDebugger.h" #include "FormatDataLibrary.h" #include "Containers/Array.h" #include "CoreMinimal.h" @@ -14,10 +14,51 @@ #include "UObject/NameTypes.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" -#include "BlueprintErrors.h" #include "FormatMessage.generated.h" +/** Format Log Verbosity: controls how a message from the "Format Log Message" + * K2Node gets written to the log. + * + * 'Fatal' is deliberately placed at the end so that the editor defaults to + * 'Error' (value 0) when the dropdown is uninitialized. This means the + * numeric values don't match ELogVerbosity, so a conversion function is needed. + * + * ThrottledDisplay and ThrottledLog behave like Display and Log respectively, + * but suppress repeated messages with the same format pattern, logging at most + * once per second. + */ +UENUM(BlueprintType) +enum class ElxFormatLogVerbosity : uint8 { + + /* Prints an error to the console and log file. The editor collects and reports errors. */ + Error, + + /* Prints a warning to the console and log file. The editor collects and report warnings. */ + Warning, + + /* Prints a message to the console and log file. */ + Display, + + /* Prints a message to the log file, however, it does not print to the console. */ + Log, + + /* Like Display, but suppresses repeated messages with the same format pattern (at most once per second). */ + ThrottledDisplay, + + /* Like Log, but suppresses repeated messages with the same format pattern (at most once per second). */ + ThrottledLog, + + /* Prints a message to a log file only if Verbose logging is enabled for the given category. This is usually used for detailed logging. */ + Verbose, + + /* Prints a message to a log file. If VeryVerbose logging is enabled, then this is used for detailed logging that would otherwise spam output. */ + VeryVerbose, + + /* Danger! Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */ + Fatal, +}; + class FBlueprintActionDatabaseRegistrar; class FString; class UEdGraph; @@ -82,7 +123,10 @@ protected: // function, which formats the message and outputs it to the log. // UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true")) - static void FormatLogMessageInternal(UObject *Context, ElxLogVerbosity Verbosity, const FString &InPattern, TArray InArgs); + static void FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray InArgs); + +private: + static ELogVerbosity::Type ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity); protected: /** When adding arguments to the node, their names are placed here and are generated as pins during construction */ diff --git a/Source/Integration/LuaCallNode.h b/Source/Integration/LuaCallNode.h index 7876c747..1667314d 100644 --- a/Source/Integration/LuaCallNode.h +++ b/Source/Integration/LuaCallNode.h @@ -2,7 +2,6 @@ #pragma once -#include "BlueprintErrors.h" #include "Containers/Array.h" #include "CoreMinimal.h" #include "EdGraph/EdGraphNode.h" @@ -13,7 +12,6 @@ #include "UObject/NameTypes.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" -#include "BlueprintErrors.h" #include "LuaCallNode.generated.h" diff --git a/Source/Integration/LuprexGameModeBase.cpp b/Source/Integration/LuprexGameModeBase.cpp index 21bc1e4d..52efdd3a 100644 --- a/Source/Integration/LuprexGameModeBase.cpp +++ b/Source/Integration/LuprexGameModeBase.cpp @@ -336,7 +336,7 @@ void ALuprexGameModeBase::InitializeGlobalState() // If somebody generates a log message that's severe enough, break to debugger. BreakToDebuggerLogVerbosityDevice.Reset( - new FlxDebugBlueprintErrorsOutputDevice(BreakToDebuggerLogVerbosity)); + new FlxBreakToDebuggerOutputDevice(BreakToDebuggerLogVerbosity)); } void ALuprexGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) diff --git a/Source/Integration/LuprexGameModeBase.h b/Source/Integration/LuprexGameModeBase.h index f9b823de..832bf820 100644 --- a/Source/Integration/LuprexGameModeBase.h +++ b/Source/Integration/LuprexGameModeBase.h @@ -10,7 +10,7 @@ #include "AssetLookup.h" #include "LuprexSockets.h" #include "TriggeredTask.h" -#include "BlueprintErrors.h" +#include "BreakToDebugger.h" #include "Blueprint/UserWidget.h" #include "Widgets/CommonActivatableWidgetContainer.h" #include "CommonActivatableWidget.h" @@ -160,7 +160,7 @@ public: // The sensitivity level at which a log message triggers a debugger breakpoint. UPROPERTY(EditAnywhere, Category="Debugging Tools") - ElxLogVerbosity BreakToDebuggerLogVerbosity; + ElxBreakToDebuggerThreshold BreakToDebuggerLogVerbosity; // The Luprex EngineWrapper, with a Mutex to protect it. // To access it, construct a FlxLockedWrapper. @@ -195,5 +195,5 @@ public: FDelegateHandle OnWorldPostActorTickHandle; // The device that implements BreakToDebuggerLogVerbosity, above. - TUniquePtr BreakToDebuggerLogVerbosityDevice; + TUniquePtr BreakToDebuggerLogVerbosityDevice; };