Refactor to use LockedWrapper guard

This commit is contained in:
2023-09-03 02:01:32 -04:00
parent 4073c88d08
commit e237c28382
7 changed files with 166 additions and 64 deletions

View File

@@ -41,10 +41,10 @@ uint32 AIntegrationGameModeBase::Run()
continue; continue;
} }
{ {
FScopeLock lk(&LuprexMutex); FLockedWrapper lockedwrap(LockableWrapper);
Sockets->Update(); Sockets->Update(lockedwrap);
Luprex.play_invoke_event_update(&Luprex, EngineSeconds); lockedwrap->play_invoke_event_update(lockedwrap.Get(), EngineSeconds);
Sockets->Update(); Sockets->Update(lockedwrap);
} }
} }
return 0; return 0;
@@ -52,6 +52,8 @@ uint32 AIntegrationGameModeBase::Run()
void AIntegrationGameModeBase::ResetToInitialState() void AIntegrationGameModeBase::ResetToInitialState()
{ {
FLockedWrapper w(LockableWrapper);
// Shut down the thread and release the ThreadEvent // Shut down the thread and release the ThreadEvent
if (Thread != nullptr) if (Thread != nullptr)
{ {
@@ -65,12 +67,16 @@ void AIntegrationGameModeBase::ResetToInitialState()
ThreadStopRequested = false; ThreadStopRequested = false;
// Release and close all sockets. // Release and close all sockets.
Sockets.Reset(); if (Sockets != nullptr)
{
Sockets->ForceCloseEverything(w);
Sockets.Reset();
}
// Delete the engine. // Delete the engine.
if (Luprex.release != nullptr) if (w->release != nullptr)
{ {
Luprex.release(&Luprex); w->release(w.Get());
} }
// Reset the clocks. // Reset the clocks.
@@ -79,15 +85,15 @@ void AIntegrationGameModeBase::ResetToInitialState()
} }
void AIntegrationGameModeBase::HandleLuprexConsoleOutput() void AIntegrationGameModeBase::HandleLuprexConsoleOutput(FLockedWrapper &w)
{ {
uint32_t ndata; const char* data; uint32_t ndata; const char* data;
Luprex.get_outgoing(&Luprex, 0, &ndata, &data); w->get_outgoing(w.Get(), 0, &ndata, &data);
if (ndata == 0) return; if (ndata == 0) return;
std::string_view src(data, ndata); std::string_view src(data, ndata);
int consumed; int consumed;
std::u16string cps = drvutil::utf8_to_ucs2(src, &consumed); std::u16string cps = drvutil::utf8_to_ucs2(src, &consumed);
Luprex.play_sent_outgoing(&Luprex, 0, consumed); w->play_sent_outgoing(w.Get(), 0, consumed);
FString fs(cps.size(), (const UCS2CHAR*)(&cps[0])); FString fs(cps.size(), (const UCS2CHAR*)(&cps[0]));
ConsoleOutput.Append(fs); ConsoleOutput.Append(fs);
} }
@@ -96,11 +102,11 @@ void AIntegrationGameModeBase::Tick(float DeltaSeconds)
{ {
Super::Tick(DeltaSeconds); Super::Tick(DeltaSeconds);
{ {
FScopeLock lk(&LuprexMutex); FLockedWrapper lockedwrap(LockableWrapper);
if (Luprex.engine != nullptr) if (lockedwrap->engine != nullptr)
{ {
EngineSeconds += DeltaSeconds; EngineSeconds += DeltaSeconds;
HandleLuprexConsoleOutput(); HandleLuprexConsoleOutput(lockedwrap);
} }
} }
TArray<FString> prints = engineutil::DPrintGetStored(); TArray<FString> prints = engineutil::DPrintGetStored();
@@ -121,9 +127,9 @@ void AIntegrationGameModeBase::Tick(float DeltaSeconds)
void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs) void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs)
{ {
if (Luprex.engine != nullptr) FLockedWrapper w(LockableWrapper);
if (w->engine != nullptr)
{ {
FScopeLock lk(&LuprexMutex);
const TCHAR* fstchar = *fs; const TCHAR* fstchar = *fs;
if (sizeof(TCHAR) == 2) if (sizeof(TCHAR) == 2)
{ {
@@ -131,7 +137,7 @@ void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs)
std::u16string_view fsview((const char16_t*)fstchar, fs.Len()); std::u16string_view fsview((const char16_t*)fstchar, fs.Len());
std::string utf8 = drvutil::utf16_to_utf8(fsview); std::string utf8 = drvutil::utf16_to_utf8(fsview);
utf8 = utf8 + "\n"; utf8 = utf8 + "\n";
Luprex.play_recv_incoming(&Luprex, 0, utf8.size(), utf8.c_str()); w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str());
} }
} }
} }
@@ -140,27 +146,26 @@ void AIntegrationGameModeBase::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
FLockedWrapper w(LockableWrapper);
// Sanity checks. // Sanity checks.
checkf(Thread == nullptr, TEXT("There should be no thread here.")); checkf(Thread == nullptr, TEXT("There should be no thread here."));
checkf(Luprex.engine == nullptr, TEXT("There should be no engine here.")); checkf(w->engine == nullptr, TEXT("There should be no engine here."));
// Make sure we're starting from a clean slate. // Make sure we're starting from a clean slate.
ResetToInitialState(); ResetToInitialState();
// Try to initialize the wrapper. // Try to initialize the wrapper.
if (Luprex.play_initialize == nullptr) w.InitWrapper();
{
engineutil::init_wrapper(&Luprex);
}
// If we failed to initialize the wrapper, print an error message. // If we failed to initialize the wrapper, print an error message.
if (Luprex.play_initialize == nullptr) if (w->play_initialize == nullptr)
{ {
engineutil::DPrint("Luprex wrapper initialization failed"); engineutil::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.
if (Luprex.play_initialize != nullptr) if (w->play_initialize != nullptr)
{ {
drvutil::ostringstream srcpak; drvutil::ostringstream srcpak;
std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak); std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak);
@@ -171,10 +176,10 @@ void AIntegrationGameModeBase::BeginPlay()
std::string_view srcpakv = srcpak.view(); std::string_view srcpakv = srcpak.view();
char* argv[1]; char* argv[1];
argv[0] = const_cast<char*>("lpxserver"); argv[0] = const_cast<char*>("lpxserver");
Luprex.play_initialize(&Luprex, 1, argv, srcpakv.size(), srcpakv.data(), ""); w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), "");
if (Luprex.error[0]) if (w->error[0])
{ {
engineutil::DPrint(Luprex.error); engineutil::DPrint(w->error);
} }
else else
{ {
@@ -183,9 +188,9 @@ void AIntegrationGameModeBase::BeginPlay()
} }
// If we successfully created a luprex engine, create a socket system and a worker thread. // If we successfully created a luprex engine, create a socket system and a worker thread.
if (Luprex.engine != nullptr) if (w->engine != nullptr)
{ {
Sockets.Reset(FLpxSockets::Create(&Luprex)); Sockets.Reset(FLpxSockets::Create(w));
std::string error = Sockets->GetError(); std::string error = Sockets->GetError();
check(error.empty()); check(error.empty());
ThreadEvent = FPlatformProcess::GetSynchEventFromPool(false); ThreadEvent = FPlatformProcess::GetSynchEventFromPool(false);

View File

@@ -45,7 +45,7 @@ public:
TSubclassOf<AActor> ClassTangibleActor; TSubclassOf<AActor> ClassTangibleActor;
// Transfer console output from the Luprex engine to unreal. // Transfer console output from the Luprex engine to unreal.
void HandleLuprexConsoleOutput(); void HandleLuprexConsoleOutput(FLockedWrapper &w);
UPROPERTY() UPROPERTY()
FTangibleManager TangibleManager; FTangibleManager TangibleManager;
@@ -65,11 +65,9 @@ public:
// to exit. // to exit.
FEvent* ThreadEvent; FEvent* ThreadEvent;
// This critical section guards the use of the EngineWrapper. // The Luprex EngineWrapper, with a Mutex to protect it.
FCriticalSection LuprexMutex; // To access it, construct a FLockedWrapper.
FLockableWrapper LockableWrapper;
// The Luprex wrapper and engine. MUST CLAIM LuprexMutex.
EngineWrapper Luprex;
// Luprex socket system. Aside from construction, only touched by Luprex thread. // Luprex socket system. Aside from construction, only touched by Luprex thread.
TUniquePtr<FLpxSockets> Sockets; TUniquePtr<FLpxSockets> Sockets;

View File

@@ -0,0 +1,19 @@
#include "LockedWrapper.h"
#include "engineutil.hpp"
void FLockedWrapper::InitWrapper() {
if (Lockable.Wrapper.play_initialize != nullptr) {
// Already initialized.
return;
}
void* DLL = FPlatformProcess::GetDllHandle(TEXT("c:\\Luprex\\build\\visual\\luprexlib.dll"));
if (DLL != nullptr) {
using InitFn = void (*)(EngineWrapper*);
InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper"));
if (init != nullptr) {
init(&Lockable.Wrapper);
Lockable.Wrapper.hook_dprint(engineutil::DPrintHook);
}
}
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include "CoreMinimal.h"
#include "enginewrapper.hpp"
// Class FLockableWrapper
//
// Contains the EngineWrapper and a Mutex to lock it.
// This class has no methods. To access the EngineWrapper,
// construct a FLockedWrapper, and then dereference it
// using operator right arrow.
//
class FLockableWrapper {
private:
FCriticalSection Mutex;
EngineWrapper Wrapper;
public:
friend class FLockedWrapper;
};
class FLockedWrapper {
private:
FLockableWrapper& Lockable;
public:
// The constructor of the FLockedWrapper claims the mutex.
FLockedWrapper(FLockableWrapper& w) : Lockable(w) {
Lockable.Mutex.Lock();
}
// The destructor of the FLockedWrapper releases the mutex.
~FLockedWrapper() {
Lockable.Mutex.Unlock();
}
// Initialize the engine wrapper if it's not already.
void InitWrapper();
// Operator right arrow accesses the EngineWrapper.
EngineWrapper* operator ->() {
return &Lockable.Wrapper;
}
// Get a pointer to the enginewrapper. This is not
// very safe because you could keep the pointer after
// the LockedWrapper is destroyed. Don't do that.
EngineWrapper* Get() {
return &Lockable.Wrapper;
}
};

View File

@@ -172,7 +172,7 @@ public:
FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state); FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state);
FLpxChannel() : FLpxChannel(nullptr, nullptr, 0, nullptr, CHAN_INACTIVE) {} FLpxChannel() : FLpxChannel(nullptr, nullptr, 0, nullptr, CHAN_INACTIVE) {}
~FLpxChannel() { Close(""); } ~FLpxChannel() { }
}; };
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
@@ -187,8 +187,8 @@ public:
// Fatal error status. // Fatal error status.
std::string FatalError; std::string FatalError;
// We don't own the wrapper, we just have a pointer to it. // This pointer is NULL except when inside
// We require a guarantee that it outlives us. // one of the methods that accepts a LockedWrapper.
EngineWrapper* Luprex; EngineWrapper* Luprex;
// A general-purpose character buffer. // A general-purpose character buffer.
@@ -206,7 +206,7 @@ public:
SSL_CTX* ClientSecureCTX; SSL_CTX* ClientSecureCTX;
SSL_CTX* ClientInsecureCTX; SSL_CTX* ClientInsecureCTX;
FLpxSocketsI(EngineWrapper* w); FLpxSocketsI(FLockedWrapper &w);
virtual ~FLpxSocketsI() override; virtual ~FLpxSocketsI() override;
// Copy the trace to the DPrint output. // Copy the trace to the DPrint output.
@@ -226,8 +226,11 @@ public:
void HandleNewOutgoingSockets(); void HandleNewOutgoingSockets();
void HandleSocketInputOutput(); void HandleSocketInputOutput();
// Force Close Everything.
virtual void ForceCloseEverything(FLockedWrapper& w);
// Main update routine. // Main update routine.
virtual void Update() override; virtual void Update(FLockedWrapper &w) override;
}; };
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
@@ -807,8 +810,11 @@ void FLpxSocketsI::SetError(const std::string& s)
} }
FLpxSocketsI::FLpxSocketsI(EngineWrapper *w) FLpxSocketsI::FLpxSocketsI(FLockedWrapper &w)
{ {
// We retain this pointer only so long as we have the wrapper lock.
Luprex = w.Get();
// This function is nonreentrant. It's not clear whether // This function is nonreentrant. It's not clear whether
// this is needed - it may be initialized elsewhere in unreal. // this is needed - it may be initialized elsewhere in unreal.
// It is also not clear that it's safe to do this in the // It is also not clear that it's safe to do this in the
@@ -816,7 +822,6 @@ FLpxSocketsI::FLpxSocketsI(EngineWrapper *w)
// thread). // thread).
SSL_library_init(); SSL_library_init();
Luprex = w;
ServerCTX = nullptr; ServerCTX = nullptr;
ClientSecureCTX = nullptr; ClientSecureCTX = nullptr;
ClientInsecureCTX = nullptr; ClientInsecureCTX = nullptr;
@@ -845,27 +850,52 @@ FLpxSocketsI::FLpxSocketsI(EngineWrapper *w)
names = names + " " + name; names = names + " " + name;
} }
HandleListenPorts(); HandleListenPorts();
// We're losing the wrapper lock, so set the pointer to nullptr.
Luprex = nullptr;
}
void FLpxSocketsI::ForceCloseEverything(FLockedWrapper& w)
{
// We retain this pointer only so long as we have the wrapper lock.
Luprex = w.Get();
// Close all channels
for (FLpxChannel& chan : Channels) {
chan.Close("Force Close Everything");
}
// Delete any channels released by the above.
RemoveInactiveChannels();
// All channels should be gone now.
check(Channels.IsEmpty());
// We're losing the wrapper lock, so set the pointer to nullptr.
Luprex = nullptr;
} }
FLpxSocketsI::~FLpxSocketsI() FLpxSocketsI::~FLpxSocketsI()
{ {
checkf(Channels.IsEmpty(), TEXT("Must call ForceCloseEverything before destructor"));
if (ServerCTX != nullptr) if (ServerCTX != nullptr)
{ {
SSL_CTX_free(ServerCTX); SSL_CTX_free(ServerCTX);
ServerCTX = nullptr;
} }
if (ClientSecureCTX != nullptr) if (ClientSecureCTX != nullptr)
{ {
SSL_CTX_free(ClientSecureCTX); SSL_CTX_free(ClientSecureCTX);
ClientSecureCTX = nullptr;
} }
if (ClientInsecureCTX != nullptr) if (ClientInsecureCTX != nullptr)
{ {
SSL_CTX_free(ClientInsecureCTX); SSL_CTX_free(ClientInsecureCTX);
ClientInsecureCTX = nullptr;
} }
// Cleanup // TODO: Be more thorough.
//for (ChanInfo& chan : chans_) {
// close_channel(chan, "");
//}
} }
void FLpxSocketsI::DPrintTrace() void FLpxSocketsI::DPrintTrace()
@@ -988,13 +1018,19 @@ void FLpxSocketsI::HandleSocketInputOutput()
RemoveInactiveChannels(); RemoveInactiveChannels();
} }
void FLpxSocketsI::Update() void FLpxSocketsI::Update(FLockedWrapper &w)
{ {
// We retain this pointer only so long as we have the wrapper lock.
Luprex = w.Get();
HandleNewOutgoingSockets(); HandleNewOutgoingSockets();
HandleSocketInputOutput(); HandleSocketInputOutput();
// We're losing the wrapper lock, so set the pointer to nullptr.
Luprex = nullptr;
} }
FLpxSockets* FLpxSockets::Create(EngineWrapper* w) FLpxSockets* FLpxSockets::Create(FLockedWrapper &w)
{ {
return new FLpxSocketsI(w); return new FLpxSocketsI(w);
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "LockedWrapper.h"
#include <string> #include <string>
// Class FLpxSockets // Class FLpxSockets
@@ -33,15 +34,19 @@ protected:
public: public:
// Create the Luprex socket system. // Create the Luprex socket system.
static FLpxSockets* Create(EngineWrapper *w); static FLpxSockets* Create(FLockedWrapper &w);
// Delete the luprex socket system. Cleanly closes all sockets. // Close all sockets.
virtual void ForceCloseEverything(FLockedWrapper& w) = 0;
// The update routine relays data into and out of
// luprex via TCP/IP sockets.
virtual void Update(FLockedWrapper& w) = 0;
// Delete the luprex socket system.
// You must call ForceCloseEverything first.
virtual ~FLpxSockets() {} virtual ~FLpxSockets() {}
// Obtain any error status report. // Obtain any error status report.
virtual std::string GetError() = 0; virtual std::string GetError() = 0;
// The update routine relays data into and out of
// luprex via TCP/IP sockets.
virtual void Update() = 0;
}; };

View File

@@ -3,17 +3,6 @@
namespace engineutil { namespace engineutil {
void init_wrapper(EngineWrapper* w) {
void* DLL = FPlatformProcess::GetDllHandle(TEXT("c:\\Luprex\\build\\visual\\luprexlib.dll"));
if (DLL != nullptr) {
using InitFn = void (*)(EngineWrapper*);
InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper"));
if (init != nullptr) {
init(w);
w->hook_dprint(engineutil::DPrintHook);
}
}
}
// The DPrint array. This stores the dprints // The DPrint array. This stores the dprints
// until they can be collected by the console implementation. // until they can be collected by the console implementation.