diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index 8d82f981..727a2cb9 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -7,6 +7,8 @@ #include #include +using namespace DebugPrint; + AIntegrationGameModeBase::AIntegrationGameModeBase() { Thread = nullptr; @@ -19,6 +21,7 @@ AIntegrationGameModeBase::AIntegrationGameModeBase() //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong SetActorTickEnabled(true); SetActorTickInterval(0.0f); + DebugPrintControl::EnableCollection(); } AIntegrationGameModeBase::~AIntegrationGameModeBase() @@ -33,11 +36,11 @@ uint32 AIntegrationGameModeBase::Run() { bool triggered = ThreadEvent->Wait(3000); if (ThreadStopRequested) { - engineutil::DPrint("Thread stopping as requested"); + DPrint("Thread stopping as requested"); break; } if (!triggered) { - engineutil::DPrint("Thread waiting a long time..."); + DPrint("Thread waiting a long time..."); continue; } { @@ -111,7 +114,7 @@ void AIntegrationGameModeBase::Tick(float DeltaSeconds) HandleLuprexConsoleOutput(lockedwrap); } } - TArray prints = engineutil::DPrintGetStored(); + TArray prints = DebugPrintControl::GetStored(); for (const FString& fs : prints) { ConsoleOutput.AppendLine(fs); } @@ -168,7 +171,7 @@ void AIntegrationGameModeBase::BeginPlay() // If we failed to initialize the wrapper, print an error message. if (w->play_initialize == nullptr) { - engineutil::DPrint("Luprex wrapper initialization failed"); + DPrint("Luprex wrapper initialization failed"); } // If wrapper is initialized, try to initialize the luprex engine. @@ -178,7 +181,7 @@ void AIntegrationGameModeBase::BeginPlay() std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak); if (!srcpakerr.empty()) { - engineutil::DPrint(srcpakerr.c_str()); + DPrint(srcpakerr.c_str()); } std::string_view srcpakv = srcpak.view(); char* argv[1]; @@ -186,11 +189,11 @@ void AIntegrationGameModeBase::BeginPlay() w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); if (w->error[0]) { - engineutil::DPrint(w->error); + DPrint(w->error); } else { - engineutil::DPrint("Luprex initialize success"); + DPrint("Luprex initialize success"); } } diff --git a/Source/Integration/IntegrationGameModeBase.h b/Source/Integration/IntegrationGameModeBase.h index 37cbc11c..964ff36c 100644 --- a/Source/Integration/IntegrationGameModeBase.h +++ b/Source/Integration/IntegrationGameModeBase.h @@ -51,7 +51,7 @@ public: FTangibleManager TangibleManager; // This stores the entire text currently visible in the console. - engineutil::ConsoleOutput ConsoleOutput; + FConsoleOutput ConsoleOutput; // The worker thread. FRunnableThread *Thread; diff --git a/Source/Integration/LockedWrapper.cpp b/Source/Integration/LockedWrapper.cpp index 1d3c0ffe..87aa7da2 100644 --- a/Source/Integration/LockedWrapper.cpp +++ b/Source/Integration/LockedWrapper.cpp @@ -13,7 +13,7 @@ void FLockedWrapper::InitWrapper() { InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper")); if (init != nullptr) { init(&Lockable.Wrapper); - Lockable.Wrapper.hook_dprint(engineutil::DPrintHook); + Lockable.Wrapper.hook_dprint(DebugPrint::DPrint); } } } \ No newline at end of file diff --git a/Source/Integration/LuprexSockets.cpp b/Source/Integration/LuprexSockets.cpp index e8a9045d..093a2d5f 100644 --- a/Source/Integration/LuprexSockets.cpp +++ b/Source/Integration/LuprexSockets.cpp @@ -8,6 +8,8 @@ #include "SocketSubsystem.h" #include "AddressInfoTypes.h" +using namespace DebugPrint; + #define UI UI_ST THIRD_PARTY_INCLUDES_START #include @@ -903,7 +905,7 @@ void FLpxSocketsI::DPrintTrace() char* data; int ndata = BIO_get_mem_data(TraceBIO, &data); if (ndata == 0) return; - engineutil::DPrintHook(data, ndata); + DPrint(data, ndata); BIO_reset(TraceBIO); } diff --git a/Source/Integration/engineutil.cpp b/Source/Integration/engineutil.cpp index fcb69e45..e86e810b 100644 --- a/Source/Integration/engineutil.cpp +++ b/Source/Integration/engineutil.cpp @@ -1,37 +1,150 @@ #include "CoreMinimal.h" #include "engineutil.hpp" -namespace engineutil { +using DPrintCallback = DebugPrintControl::DPrintCallback; + +////////////////////////////////////////////////////////////// +// +// DPrintState +// +// The global state of the DPrint routine. There is only +// ever one of these, and it is owned by DPrintAccess below. +// +////////////////////////////////////////////////////////////// + +struct DPrintState { + // True if buffering is enabled. + bool Collect; + + // The array of buffered messages. + TArray Messages; + + // The array of callback functions. + TArray Callbacks; +}; + +////////////////////////////////////////////////////////////// +// +// DPrintAccess +// +// This class grants you safe access to the global state +// of the DPrint routine. Constructing an object of +// class DPrintAccess will lock the global mutex and make +// sure that the DPrintState is initialized. Then it will +// give you unrestricted access to the DPrintState through +// operator right arrow. +// +////////////////////////////////////////////////////////////// + +class DPrintAccess { +private: + // The Mutex that protects the global state. + static FCriticalSection Mutex; + + // The Global State. A pointer, to avoid static init issues. + static DPrintState* State; + +public: + // Constructor. Locks mutex and initializes state if necessary. + DPrintAccess() { + Mutex.Lock(); + if (State == nullptr) { + State = new DPrintState; + State->Collect = false; + } + } + + // Destructor. Releases mutex. + ~DPrintAccess() { + Mutex.Unlock(); + } + + // Access the DPrintState. + DPrintState* operator ->() { + return State; + } +}; + +FCriticalSection DPrintAccess::Mutex; +DPrintState* DPrintAccess::State; -// The DPrint array. This stores the dprints -// until they can be collected by the console implementation. -static TArray dprint_array; -static FCriticalSection dprint_mutex; +////////////////////////////////////////////////////////////// +// +// Namespace DebugPrint. +// +// This contains all the various versions of the DPrint routine. +// +////////////////////////////////////////////////////////////// -void DPrint(const FString& fs) { - FScopeLock lk(&dprint_mutex); - dprint_array.Emplace(fs); -} +namespace DebugPrint { -void DPrint(const char* msg) { - FScopeLock lk(&dprint_mutex); - dprint_array.Emplace(msg); -} + // DPrint. Invoke all the callbacks, and store the message. + void DPrint(const FString& fs) { + DPrintAccess state; + for (DPrintCallback cb : state->Callbacks) { + cb(fs); + } + if (state->Collect) { + state->Messages.Emplace(fs); + } + } -void DPrintHook(const char* msg, size_t len) { - FScopeLock lk(&dprint_mutex); - dprint_array.Emplace(len, (const UTF8CHAR*)msg); -} + // Alternative interface to the dispatcher. + void DPrint(const char* msg) { + DPrint(FString(msg)); + } -TArray DPrintGetStored() { - FScopeLock lk(&dprint_mutex); - TArray result = std::move(dprint_array); - dprint_array.Empty(); - return result; -} + // Alternative interface to the dispatcher. + void DPrint(const char* msg, size_t len) { + DPrint(FString(len, (const UTF8CHAR*)msg)); + } -void ConsoleOutput::Append(const FString& text) { +} // namespace DebugPrint + +////////////////////////////////////////////////////////////// +// +// Namespace DebugPrintControl. +// +// Configuration and control for the DPrint routine. +// +////////////////////////////////////////////////////////////// + +namespace DebugPrintControl { + + // Register a callback. Check for duplication. + void RegisterCallback(DPrintCallback cb) { + DPrintAccess state; + for (DPrintCallback old : state->Callbacks) { + if (cb == old) return; + } + state->Callbacks.Add(cb); + } + + // Set the collection bit in the global state. + void EnableCollection() { + DPrintAccess state; + state->Collect = true; + } + + // Get the array of collected messages, if any. + TArray GetStored() { + DPrintAccess state; + TArray result = std::move(state->Messages); + state->Messages.Empty(); + return result; + } +}; + +////////////////////////////////////////////////////////////// +// +// ConsoleOutput +// +// Storing the text that goes in the unreal console. +// +////////////////////////////////////////////////////////////// + +void FConsoleOutput::Append(const FString& text) { if (!text.IsEmpty()) { Content += text; Truncate(); @@ -39,7 +152,7 @@ void ConsoleOutput::Append(const FString& text) { } } -void ConsoleOutput::AppendLine(const FString& text) { +void FConsoleOutput::AppendLine(const FString& text) { int csize = Content.Len(); if ((csize > 0) && (Content[csize - 1] != '\n')) { Content += TEXT("\n"); @@ -50,7 +163,7 @@ void ConsoleOutput::AppendLine(const FString& text) { Dirty = true; } -void ConsoleOutput::Truncate() { +void FConsoleOutput::Truncate() { int lines = 50; int csize = Content.Len(); int total = 0; @@ -65,5 +178,3 @@ void ConsoleOutput::Truncate() { } } -} // namespace engineutil - diff --git a/Source/Integration/engineutil.hpp b/Source/Integration/engineutil.hpp index 762eefdf..7bcfe4ae 100644 --- a/Source/Integration/engineutil.hpp +++ b/Source/Integration/engineutil.hpp @@ -1,24 +1,119 @@ #pragma once -#include "enginewrapper.hpp" +////////////////////////////////////////////////////////////// +// +// Namespace DebugPrint +// +// This namespace contains the function "DPrint", +// which is short for debugging print. This +// is what you should use if you want to print +// a debugging message. +// +// Messages printed using "DPrint" will +// be routed to several destinations. These +// might include a logfile, the unreal console, +// and the visual studio debugging log. +// +// In order for "DPrint" to work, you have to +// configure them once, at program initialization +// time. This is done using the functions in +// namespace DebugPrintConfig, below. +// +// We promise that namespace DebugPrint will +// only ever contain one function, 'DPrint.' +// that way, it's safe for you to put +// 'using namespace DebugPrint' in your code. +// +// All DPrint-related functionality is +// thread-safe. A single 'DPrint' is meant to +// be output atomically. +// +// It is fine for the content of the DPrint +// string to contain newlines. +// +////////////////////////////////////////////////////////////// -namespace engineutil { -// Load the DLL and initialize the wrapper, if possible. -void init_wrapper(EngineWrapper* w); +namespace DebugPrint { + // Print text into the debug log. + void DPrint(const FString& fs); -// Print text on the console. -void DPrint(const FString& fs); + // Print text into the debug log. + void DPrint(const char* msg); -// Print text on the console. -void DPrint(const char* msg); + // Print text into the debug log. + void DPrint(const char* msg, size_t len); +}; -// Print text on the console. -void DPrintHook(const char* msg, size_t len); +////////////////////////////////////////////////////////////// +// +// namespace DebugPrintControl +// +// This namespace contains the functions +// necessary to initialize "DPrint", and +// route its output to several destinations. +// +// "DPrint" is a callback-based design. +// Initialization might consist of: register a +// callback that sends a string to a logfile, +// register another callback that sends a string +// to visual studio's debug console, and register +// a third one that sends a string to the +// unreal console. Each time somebody calls +// 'DPrint', all three callbacks will get invoked. +// +// There is also a 'collect' option where you +// can ask "DPrint" to save the messages in a +// buffer, which you can then collect at +// your leisure. You can use buffering and +// callbacks at the same time. Note that +// buffering is inherently less than ideal for +// messages that warn of imminent program aborts. +// So it is recommended that you use at least one +// callback that sends its output without delay. +// +////////////////////////////////////////////////////////////// -// Get all the stored dprints. -TArray DPrintGetStored(); +namespace DebugPrintControl { + // The prototype for a callback function. + // + using DPrintCallback = void (*)(const FString& fs); -class ConsoleOutput { + // Register a callback. + // + // Registering a callback that is already + // registered is a no-op. + // + void RegisterCallback(DPrintCallback f); + + // Enable collection in a buffer. + // + // If collection is already enabled, this is a no-op. + // + void EnableCollection(); + + // Collect all the stored messages, and clear the storage. + // + TArray GetStored(); +}; + +////////////////////////////////////////////////////////////// +// +// 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. +// +////////////////////////////////////////////////////////////// + +class FConsoleOutput { private: FString Content; bool Dirty; @@ -44,5 +139,3 @@ public: }; -} // namespace engineutil -