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_view>
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<FString> prints = engineutil::DPrintGetStored();
TArray<FString> 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");
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -8,6 +8,8 @@
#include "SocketSubsystem.h"
#include "AddressInfoTypes.h"
using namespace DebugPrint;
#define UI UI_ST
THIRD_PARTY_INCLUDES_START
#include <openssl/ssl.h>
@@ -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);
}

View File

@@ -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<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;
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<FString> DPrintGetStored() {
FScopeLock lk(&dprint_mutex);
TArray<FString> result = std::move(dprint_array);
dprint_array.Empty();
// Alternative interface to the dispatcher.
void DPrint(const char* msg, size_t len) {
DPrint(FString(len, (const UTF8CHAR*)msg));
}
} // 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;
}
}
};
void ConsoleOutput::Append(const FString& text) {
//////////////////////////////////////////////////////////////
//
// 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

View File

@@ -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<FString> 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<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:
FString Content;
bool Dirty;
@@ -44,5 +139,3 @@ public:
};
} // namespace engineutil