Major overhaul of DebugPrint

This commit is contained in:
2023-09-04 03:21:23 -04:00
parent 57ea8d8574
commit e3c20b3f1e
6 changed files with 262 additions and 53 deletions

View File

@@ -7,6 +7,8 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
using namespace DebugPrint;
AIntegrationGameModeBase::AIntegrationGameModeBase() AIntegrationGameModeBase::AIntegrationGameModeBase()
{ {
Thread = nullptr; Thread = nullptr;
@@ -19,6 +21,7 @@ AIntegrationGameModeBase::AIntegrationGameModeBase()
//PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong
SetActorTickEnabled(true); SetActorTickEnabled(true);
SetActorTickInterval(0.0f); SetActorTickInterval(0.0f);
DebugPrintControl::EnableCollection();
} }
AIntegrationGameModeBase::~AIntegrationGameModeBase() AIntegrationGameModeBase::~AIntegrationGameModeBase()
@@ -33,11 +36,11 @@ uint32 AIntegrationGameModeBase::Run()
{ {
bool triggered = ThreadEvent->Wait(3000); bool triggered = ThreadEvent->Wait(3000);
if (ThreadStopRequested) { if (ThreadStopRequested) {
engineutil::DPrint("Thread stopping as requested"); DPrint("Thread stopping as requested");
break; break;
} }
if (!triggered) { if (!triggered) {
engineutil::DPrint("Thread waiting a long time..."); DPrint("Thread waiting a long time...");
continue; continue;
} }
{ {
@@ -111,7 +114,7 @@ void AIntegrationGameModeBase::Tick(float DeltaSeconds)
HandleLuprexConsoleOutput(lockedwrap); HandleLuprexConsoleOutput(lockedwrap);
} }
} }
TArray<FString> prints = engineutil::DPrintGetStored(); TArray<FString> prints = DebugPrintControl::GetStored();
for (const FString& fs : prints) { for (const FString& fs : prints) {
ConsoleOutput.AppendLine(fs); ConsoleOutput.AppendLine(fs);
} }
@@ -168,7 +171,7 @@ void AIntegrationGameModeBase::BeginPlay()
// If we failed to initialize the wrapper, print an error message. // If we failed to initialize the wrapper, print an error message.
if (w->play_initialize == nullptr) 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. // 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); std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak);
if (!srcpakerr.empty()) if (!srcpakerr.empty())
{ {
engineutil::DPrint(srcpakerr.c_str()); DPrint(srcpakerr.c_str());
} }
std::string_view srcpakv = srcpak.view(); std::string_view srcpakv = srcpak.view();
char* argv[1]; char* argv[1];
@@ -186,11 +189,11 @@ void AIntegrationGameModeBase::BeginPlay()
w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), "");
if (w->error[0]) if (w->error[0])
{ {
engineutil::DPrint(w->error); DPrint(w->error);
} }
else else
{ {
engineutil::DPrint("Luprex initialize success"); DPrint("Luprex initialize success");
} }
} }

View File

@@ -51,7 +51,7 @@ public:
FTangibleManager TangibleManager; FTangibleManager TangibleManager;
// This stores the entire text currently visible in the console. // This stores the entire text currently visible in the console.
engineutil::ConsoleOutput ConsoleOutput; FConsoleOutput ConsoleOutput;
// The worker thread. // The worker thread.
FRunnableThread *Thread; FRunnableThread *Thread;

View File

@@ -13,7 +13,7 @@ void FLockedWrapper::InitWrapper() {
InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper")); InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper"));
if (init != nullptr) { if (init != nullptr) {
init(&Lockable.Wrapper); init(&Lockable.Wrapper);
Lockable.Wrapper.hook_dprint(engineutil::DPrintHook); Lockable.Wrapper.hook_dprint(DebugPrint::DPrint);
} }
} }
} }

View File

@@ -8,6 +8,8 @@
#include "SocketSubsystem.h" #include "SocketSubsystem.h"
#include "AddressInfoTypes.h" #include "AddressInfoTypes.h"
using namespace DebugPrint;
#define UI UI_ST #define UI UI_ST
THIRD_PARTY_INCLUDES_START THIRD_PARTY_INCLUDES_START
#include <openssl/ssl.h> #include <openssl/ssl.h>
@@ -903,7 +905,7 @@ void FLpxSocketsI::DPrintTrace()
char* data; char* data;
int ndata = BIO_get_mem_data(TraceBIO, &data); int ndata = BIO_get_mem_data(TraceBIO, &data);
if (ndata == 0) return; if (ndata == 0) return;
engineutil::DPrintHook(data, ndata); DPrint(data, ndata);
BIO_reset(TraceBIO); BIO_reset(TraceBIO);
} }

View File

@@ -1,37 +1,150 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "engineutil.hpp" #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<FString> Messages;
// The array of callback functions.
TArray<DPrintCallback> 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<FString> dprint_array; // Namespace DebugPrint.
static FCriticalSection dprint_mutex; //
// This contains all the various versions of the DPrint routine.
//
//////////////////////////////////////////////////////////////
void DPrint(const FString& fs) { namespace DebugPrint {
FScopeLock lk(&dprint_mutex);
dprint_array.Emplace(fs);
}
void DPrint(const char* msg) { // DPrint. Invoke all the callbacks, and store the message.
FScopeLock lk(&dprint_mutex); void DPrint(const FString& fs) {
dprint_array.Emplace(msg); DPrintAccess state;
} for (DPrintCallback cb : state->Callbacks) {
cb(fs);
}
if (state->Collect) {
state->Messages.Emplace(fs);
}
}
void DPrintHook(const char* msg, size_t len) { // Alternative interface to the dispatcher.
FScopeLock lk(&dprint_mutex); void DPrint(const char* msg) {
dprint_array.Emplace(len, (const UTF8CHAR*)msg); DPrint(FString(msg));
} }
TArray<FString> DPrintGetStored() { // Alternative interface to the dispatcher.
FScopeLock lk(&dprint_mutex); void DPrint(const char* msg, size_t len) {
TArray<FString> result = std::move(dprint_array); DPrint(FString(len, (const UTF8CHAR*)msg));
dprint_array.Empty(); }
return result;
}
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<FString> GetStored() {
DPrintAccess state;
TArray<FString> 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()) { if (!text.IsEmpty()) {
Content += text; Content += text;
Truncate(); 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(); int csize = Content.Len();
if ((csize > 0) && (Content[csize - 1] != '\n')) { if ((csize > 0) && (Content[csize - 1] != '\n')) {
Content += TEXT("\n"); Content += TEXT("\n");
@@ -50,7 +163,7 @@ void ConsoleOutput::AppendLine(const FString& text) {
Dirty = true; Dirty = true;
} }
void ConsoleOutput::Truncate() { void FConsoleOutput::Truncate() {
int lines = 50; int lines = 50;
int csize = Content.Len(); int csize = Content.Len();
int total = 0; int total = 0;
@@ -65,5 +178,3 @@ void ConsoleOutput::Truncate() {
} }
} }
} // namespace engineutil

View File

@@ -1,24 +1,119 @@
#pragma once #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 { namespace DebugPrint {
// Load the DLL and initialize the wrapper, if possible. // Print text into the debug log.
void init_wrapper(EngineWrapper* w); void DPrint(const FString& fs);
// Print text on the console. // Print text into the debug log.
void DPrint(const FString& fs); void DPrint(const char* msg);
// Print text on the console. // Print text into the debug log.
void DPrint(const char* msg); 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. namespace DebugPrintControl {
TArray<FString> DPrintGetStored(); // 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<FString> 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: private:
FString Content; FString Content;
bool Dirty; bool Dirty;
@@ -44,5 +139,3 @@ public:
}; };
} // namespace engineutil