Skip to content

Commit

Permalink
NetImGui: gracefully handle connection / reconnection
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed Apr 10, 2024
1 parent e61894b commit a67e9f3
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 95 deletions.
252 changes: 160 additions & 92 deletions src/hello_imgui/internal/backend_impls/abstract_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,162 @@ namespace HelloImGui
void setFinalAppWindowScreenshotRgbBuffer(const ImageBuffer& b);


// =====================================================================================================================
// <NetImGuiWrapper>
// =====================================================================================================================
#ifdef HELLOIMGUI_WITH_NETIMGUI
struct NetImGuiRaii {
NetImGuiRaii() { NetImgui::Startup(); }
~NetImGuiRaii() { NetImgui::Shutdown(); }
};


class NetImGuiWrapper
{
public:
NetImGuiWrapper() = default;
~NetImGuiWrapper() = default;

void HeartBeat()
{
if (mNetImguiRaii == nullptr)
{
InitiateConnection();
return;
}

int nbConnectionsPending = mNbConnectionsTentatives - mNbConnectionsSuccess - mNbConnectionsFailures;
IM_ASSERT(nbConnectionsPending == 0 || nbConnectionsPending == 1);

if (nbConnectionsPending == 1)
{
if (NetImgui::IsConnected())
{
LogStatus("Connection success...\n");
++ mNbConnectionsSuccess;
mLastConnectionSuccess = true;
}
else if (NetImgui::IsConnectionPending())
{
LogStatus("Connection pending...\n");
}
else
{
LogStatus("Connection failure...\n");
++ mNbConnectionsFailures;
mLastConnectionSuccess = false;
}
}
else // nbConnectionsPending == 0
{
if (mLastConnectionSuccess)
{
if (!NetImgui::IsConnected())
{
if (remoteParams().exitWhenServerDisconnected)
{
LogStatus("Connection lost... Exiting\n");
runnerParams().appShallExit = true;
}
else
{
mTimePointConnectionEnded = HelloImGui::Internal::ClockSeconds();
LogStatus("Connection lost... Reconnecting\n");
InitiateConnection();
}
}
}
else // Last connection was a failure
{
LogStatus("Last connection was a failure... Reconnecting\n");
InitiateConnection();
}
}

// Exit if failure since too long
{
bool isDisconnected = !mLastConnectionSuccess;
if (isDisconnected)
{
double disconnectionTime = HelloImGui::Internal::ClockSeconds() - mTimePointConnectionEnded;
std::string msg = "Disconnected since " + std::to_string(disconnectionTime) + "s";
LogStatus(msg);
bool isTooLong = disconnectionTime > remoteParams().durationMaxDisconnected;
if (isTooLong)
{
LogStatus("Too long disconnected... Exiting\n");
runnerParams().appShallExit = true;
}
}
}
}

private:
void InitiateConnection()
{
printf("NetImGuiWrapper: InitiateConnection\n");
mNetImguiRaii.release();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mNetImguiRaii = std::make_unique<NetImGuiRaii>();
NetImgui::ConnectToApp(clientName().c_str(), remoteParams().serverHost.c_str(), remoteParams().serverPort);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
++ mNbConnectionsTentatives;
sendFonts();
}

std::string clientName()
{
std::string clientName = remoteParams().clientName;
if (clientName.empty())
clientName = runnerParams().appWindowParams.windowTitle;
if (clientName.empty())
clientName = "HelloImGui";
clientName += "##" + std::to_string(ImGui::GetTime());
return clientName;
}

HelloImGui::NetImGuiParams& remoteParams() { return HelloImGui::GetRunnerParams()->remoteParams; }
HelloImGui::RunnerParams& runnerParams() { return *HelloImGui::GetRunnerParams(); }

void sendFonts()
{
printf("NetImGuiWrapper: sendFonts\n");
const ImFontAtlas* pFonts = ImGui::GetIO().Fonts;
if( pFonts->TexPixelsAlpha8) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID ))
{
uint8_t* pPixelData(nullptr); int width(0), height(0);
ImGui::GetIO().Fonts->GetTexDataAsAlpha8(&pPixelData, &width, &height);
NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast<uint16_t>(width), static_cast<uint16_t>(height), NetImgui::eTexFormat::kTexFmtA8);
}
if( pFonts->TexPixelsRGBA32) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID ))
{
uint8_t* pPixelData(nullptr); int width(0), height(0);
ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pPixelData, &width, &height);
NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast<uint16_t>(width), static_cast<uint16_t>(height), NetImgui::eTexFormat::kTexFmtRGBA8);
}
}

void LogStatus(const std::string& msg)
{
printf("NetImGuiWrapper: %s\n", msg.c_str());
}

private: // Members
std::unique_ptr<NetImGuiRaii> mNetImguiRaii;
int mNbConnectionsTentatives = 0;
int mNbConnectionsSuccess = 0;
int mNbConnectionsFailures = 0;
bool mLastConnectionSuccess = false;
double mTimePointConnectionEnded = 0.0;
};

std::unique_ptr<NetImGuiWrapper> gNetImGuiWrapper;
#endif
// =====================================================================================================================
// </NetImGuiWrapper>
// =====================================================================================================================


AbstractRunner::AbstractRunner(RunnerParams &params_)
: params(params_) {}

Expand Down Expand Up @@ -688,90 +844,7 @@ void AbstractRunner::Setup()

#ifdef HELLOIMGUI_WITH_NETIMGUI
if (params.remoteParams.enableRemoting)
{
if (!NetImGui_Connect())
{
IM_ASSERT(false && "NetImGui_Connect() failed! Please launch the server/display app before!");
exit(1);
}
}
#endif
}


void AbstractRunner::NetImGui_LogConnectionStatusOnce()
{
if (! params.remoteParams.enableRemoting)
return;
#ifdef HELLOIMGUI_WITH_NETIMGUI
// Log connection status and return
if (NetImgui::IsConnectionPending())
{
static bool wasMessageShown = false;
if (!wasMessageShown)
{
wasMessageShown = true;
printf("Waiting for NetImgui connection...\n");
}
}
else if (NetImgui::IsConnected())
{
static bool wasMessageShown = false;
if (!wasMessageShown)
{
wasMessageShown = true;
printf("Connected to NetImgui server\n");
}
}
#endif
}


bool AbstractRunner::NetImGui_Connect()
{
#ifdef HELLOIMGUI_WITH_NETIMGUI
// 0. Startup NetImgui
NetImgui::Startup();
// 1. Connect to NetImgui server
std::string clientName = params.remoteParams.clientName;
if (clientName.empty())
clientName = params.appWindowParams.windowTitle;
if (clientName.empty())
clientName = "HelloImGui";
bool clientActiveImmediately = NetImgui::ConnectToApp(clientName.c_str(), params.remoteParams.serverHost.c_str(), params.remoteParams.serverPort);

// 2. Wait a bit for the connection to be established (this is optional, just to display the messages after)
if (!clientActiveImmediately)
{
printf("Sleeping 0.1s to wait for NetImgui connection...\n");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

// 3. Log connection status (this is optional also, since this is called also inside CreateFramesAndRender)
NetImGui_LogConnectionStatusOnce();
if (!NetImgui::IsConnected() && !NetImgui::IsConnectionPending())
{
printf("Connection to NetImgui server failed!\n");
return false;
}

// 4. Send Font texture to NetImgui server
const ImFontAtlas* pFonts = ImGui::GetIO().Fonts;
if( pFonts->TexPixelsAlpha8) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID ))
{
uint8_t* pPixelData(nullptr); int width(0), height(0);
ImGui::GetIO().Fonts->GetTexDataAsAlpha8(&pPixelData, &width, &height);
NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast<uint16_t>(width), static_cast<uint16_t>(height), NetImgui::eTexFormat::kTexFmtA8);
}
if( pFonts->TexPixelsRGBA32) // && (pFonts->TexPixelsAlpha8 != client.mpFontTextureData || client.mFontTextureID != pFonts->TexID ))
{
uint8_t* pPixelData(nullptr); int width(0), height(0);
ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pPixelData, &width, &height);
NetImgui::SendDataTexture(pFonts->TexID, pPixelData, static_cast<uint16_t>(width), static_cast<uint16_t>(height), NetImgui::eTexFormat::kTexFmtRGBA8);
}
return true;
#else
return false;
gNetImGuiWrapper = std::make_unique<NetImGuiWrapper>();
#endif
}

Expand Down Expand Up @@ -872,13 +945,7 @@ void AbstractRunner::CreateFramesAndRender()
#ifdef HELLOIMGUI_WITH_NETIMGUI
if (params.remoteParams.enableRemoting) // Hande NetImGui connection
{
NetImGui_LogConnectionStatusOnce();
if (!NetImgui::IsConnected())
{
// Maybe we should wait for a few frames before we exit.
printf("NetImGui: Not connected anymore, exiting app\n");
params.appShallExit = true;
}
gNetImGuiWrapper->HeartBeat();
}
#endif

Expand Down Expand Up @@ -1252,7 +1319,8 @@ void AbstractRunner::TearDown(bool gotException)
#ifdef HELLOIMGUI_WITH_NETIMGUI
if (params.remoteParams.enableRemoting)
{
NetImgui::Shutdown();
IM_ASSERT(gNetImGuiWrapper != nullptr);
gNetImGuiWrapper.release();
}
#endif
}
Expand Down
3 changes: 0 additions & 3 deletions src/hello_imgui/internal/backend_impls/abstract_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,6 @@ class AbstractRunner
void LayoutSettings_Load();
void LayoutSettings_Save();

bool NetImGui_Connect();
void NetImGui_LogConnectionStatusOnce();

public:
BackendApi::WindowPointer mWindow = nullptr;
protected:
Expand Down
3 changes: 3 additions & 0 deletions src/hello_imgui/runner_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ struct NetImGuiParams
{
bool enableRemoting = false;

bool exitWhenServerDisconnected = false;
double durationMaxDisconnected = 30.0;

// Name of the client (if empty, will use params.appWindowParams.windowTitle)
// (The client is the app that contains the application logic)
std::string clientName = "";
Expand Down

0 comments on commit a67e9f3

Please sign in to comment.