Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
// Google modifications:
// - Replaced yield() with sleep_for(1ms) in handshake wait loop to reduce
// CPU usage, and added a 5-second timeout to prevent infinite hangs when
// the server is unreachable.
// - Added cmdTexture.mpNext = nullptr in TexturePendingServerAdd to
// terminate the linked list when appending a new texture command.
// - Added logging for network events.
#include "NetImgui_Shared.h"
#include "google/logging.h"
#if NETIMGUI_ENABLED
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <chrono>
#include <thread>
#include "NetImgui_Client.h"
#include "NetImgui_CmdPackets.h"
#include "NetImgui_Network.h"
#include "NetImgui_WarningDisable.h"
namespace NetImgui {
namespace Internal {
namespace Client {
//=================================================================================================
// SAVED IMGUI CONTEXT
// Because we overwrite some Imgui context IO values, we save them before makign
// any change and restore them after detecting a disconnection
//=================================================================================================
void SavedImguiContext::Save(ImGuiContext* copyFrom) {
ScopedImguiContext scopedContext(copyFrom);
ImGuiIO& sourceIO = ImGui::GetIO();
mSavedContext = true;
mConfigFlags = sourceIO.ConfigFlags;
mBackendFlags = sourceIO.BackendFlags;
mBackendPlatformName = sourceIO.BackendPlatformName;
mBackendRendererName = sourceIO.BackendRendererName;
mDrawMouse = sourceIO.MouseDrawCursor;
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
mFontGeneratedSize =
sourceIO.Fonts->Fonts.Size > 0
? sourceIO.Fonts->Fonts[0]->FontSize
: 13.f; // Save size to restore the font to original size
mFontGlobalScale = sourceIO.FontGlobalScale;
#endif
#if IMGUI_VERSION_NUM < 18700
memcpy(mKeyMap, sourceIO.KeyMap, sizeof(mKeyMap));
#endif
#if IMGUI_VERSION_NUM < 19110
mGetClipboardTextFn = sourceIO.GetClipboardTextFn;
mSetClipboardTextFn = sourceIO.SetClipboardTextFn;
mClipboardUserData = sourceIO.ClipboardUserData;
#else
ImGuiPlatformIO& plaformIO = ImGui::GetPlatformIO();
mGetClipboardTextFn = plaformIO.Platform_GetClipboardTextFn;
mSetClipboardTextFn = plaformIO.Platform_SetClipboardTextFn;
mClipboardUserData = plaformIO.Platform_ClipboardUserData;
#endif
}
void SavedImguiContext::Restore(ImGuiContext* copyTo) {
ScopedImguiContext scopedContext(copyTo);
ImGuiIO& destIO = ImGui::GetIO();
mSavedContext = false;
destIO.ConfigFlags = mConfigFlags;
destIO.BackendFlags = mBackendFlags;
destIO.BackendPlatformName = mBackendPlatformName;
destIO.BackendRendererName = mBackendRendererName;
destIO.MouseDrawCursor = mDrawMouse;
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
destIO.FontGlobalScale = mFontGlobalScale;
#endif
#if IMGUI_VERSION_NUM < 18700
memcpy(destIO.KeyMap, mKeyMap, sizeof(destIO.KeyMap));
#endif
#if IMGUI_VERSION_NUM < 19110
destIO.GetClipboardTextFn = mGetClipboardTextFn;
destIO.SetClipboardTextFn = mSetClipboardTextFn;
destIO.ClipboardUserData = mClipboardUserData;
#else
ImGuiPlatformIO& plaformIO = ImGui::GetPlatformIO();
plaformIO.Platform_GetClipboardTextFn = mGetClipboardTextFn;
plaformIO.Platform_SetClipboardTextFn = mSetClipboardTextFn;
plaformIO.Platform_ClipboardUserData = mClipboardUserData;
#endif
}
//=================================================================================================
// GET CLIPBOARD
// Content received from the Server
//=================================================================================================
#if IMGUI_VERSION_NUM < 19110
static const char* GetClipboardTextFn_NetImguiImpl(void* user_data_ctx) {
const ClientInfo* pClient =
reinterpret_cast<const ClientInfo*>(user_data_ctx);
return pClient && pClient->mpCmdClipboard
? pClient->mpCmdClipboard->mContentUTF8.Get()
: nullptr;
}
#else
static const char* GetClipboardTextFn_NetImguiImpl(ImGuiContext* ctx) {
ScopedImguiContext scopedContext(ctx);
const ClientInfo* pClient = reinterpret_cast<const ClientInfo*>(
ImGui::GetPlatformIO().Platform_ClipboardUserData);
return pClient && pClient->mpCmdClipboard
? pClient->mpCmdClipboard->mContentUTF8.Get()
: nullptr;
}
#endif
//=================================================================================================
// SET CLIPBOARD
//=================================================================================================
#if IMGUI_VERSION_NUM < 19110
static void SetClipboardTextFn_NetImguiImpl(void* user_data_ctx,
const char* text) {
if (user_data_ctx) {
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(user_data_ctx);
CmdClipboard* pClipboardOut = CmdClipboard::Create(text);
pClient->mPendingClipboardOut.Assign(pClipboardOut);
}
}
#else
static void SetClipboardTextFn_NetImguiImpl(ImGuiContext* ctx,
const char* text) {
ScopedImguiContext scopedContext(ctx);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(
ImGui::GetPlatformIO().Platform_ClipboardUserData);
CmdClipboard* pClipboardOut = CmdClipboard::Create(text);
pClient->mPendingClipboardOut.Assign(pClipboardOut);
}
#endif
//=================================================================================================
// INCOM: INPUT
// Receive new keyboard/mouse/screen resolution input to pass on to dearImgui
//=================================================================================================
void Communications_Incoming_Input(ClientInfo& client) {
auto pCmdInput = static_cast<CmdInput*>(client.mPendingRcv.pCommand);
client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
client.mDesiredFps =
pCmdInput->mDesiredFps > 0.f ? pCmdInput->mDesiredFps : 0.f;
size_t keyCount(pCmdInput->mKeyCharCount);
client.mPendingKeyIn.AddData(pCmdInput->mKeyChars, keyCount);
VLOG(2, "CmdInput: mDesiredFps=%.1f, screen=%dx%d, mouse=%d,%d",
pCmdInput->mDesiredFps, pCmdInput->mScreenSize[0],
pCmdInput->mScreenSize[1], pCmdInput->mMousePos[0],
pCmdInput->mMousePos[1]);
client.mPendingInputIn.Assign(pCmdInput);
}
//=================================================================================================
// INCOM: CLIPBOARD
// Receive server new clipboard content, updating internal cache
//=================================================================================================
void Communications_Incoming_Clipboard(ClientInfo& client) {
auto pCmdClipboard = static_cast<CmdClipboard*>(client.mPendingRcv.pCommand);
client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdClipboard->ToPointers();
client.mPendingClipboardIn.Assign(pCmdClipboard);
}
//=================================================================================================
// OUTCOM: TEXTURE
// Transmit the next pending texture command
//=================================================================================================
void Communications_Outgoing_Textures(ClientInfo& client) {
if (client.mPendingTextures) {
std::lock_guard<std::mutex> guard(client.mPendingTexturesLock);
if (client.mPendingTextures) {
// Get oldest texture command to sent to server
CmdTexture* pPendingTexture = client.mPendingTextures;
client.mPendingTextures = client.mPendingTextures->mpNext;
pPendingTexture->mpNext = nullptr;
client.mPendingSend.pCommand = pPendingTexture;
client.mPendingSend.bAutoFree =
false; // free handled by main update thread
}
}
}
//=================================================================================================
// OUTCOM: BACKGROUND
// Transmit the current client background settings
//=================================================================================================
void Communications_Outgoing_Background(ClientInfo& client) {
CmdBackground* pPendingBackground = client.mPendingBackgroundOut.Release();
if (pPendingBackground) {
client.mPendingSend.pCommand = pPendingBackground;
client.mPendingSend.bAutoFree = false;
}
}
//=================================================================================================
// OUTCOM: FRAME
// Transmit a new dearImgui frame to render
//=================================================================================================
void Communications_Outgoing_Frame(ClientInfo& client) {
CmdDrawFrame* pPendingDraw = client.mPendingFrameOut.Release();
if (pPendingDraw) {
pPendingDraw->mFrameIndex = client.mFrameIndex++;
//---------------------------------------------------------------------
// Apply delta compression to DrawCommand, when requested
if (pPendingDraw->mCompressed) {
// Create a new Compressed DrawFrame Command
if (client.mpCmdDrawLast && !client.mServerCompressionSkip) {
client.mpCmdDrawLast->ToPointers();
CmdDrawFrame* pDrawCompressed =
CompressCmdDrawFrame(client.mpCmdDrawLast, pPendingDraw);
netImguiDeleteSafe(client.mpCmdDrawLast);
client.mpCmdDrawLast = pPendingDraw; // Keep original new command for
// next frame delta compression
pPendingDraw =
pDrawCompressed; // Request compressed copy to be sent to server
}
// Save DrawCmd for next frame delta compression
else {
pPendingDraw->mCompressed = false;
client.mpCmdDrawLast = pPendingDraw;
}
}
client.mServerCompressionSkip = false;
//---------------------------------------------------------------------
// Ready to send command to server
pPendingDraw->ToOffsets();
client.mPendingSend.pCommand = pPendingDraw;
client.mPendingSend.bAutoFree = client.mpCmdDrawLast != pPendingDraw;
}
}
//=================================================================================================
// OUTCOM: Clipboard
// Send client 'Copy' clipboard content to Server
//=================================================================================================
void Communications_Outgoing_Clipboard(ClientInfo& client) {
CmdClipboard* pPendingClipboard = client.mPendingClipboardOut.Release();
if (pPendingClipboard) {
pPendingClipboard->ToOffsets();
client.mPendingSend.pCommand = pPendingClipboard;
client.mPendingSend.bAutoFree = true;
}
}
//=================================================================================================
// INCOMING COMMUNICATIONS
//=================================================================================================
void Communications_Incoming(ClientInfo& client) {
if (Network::DataReceivePending(client.mpSocketComs)) {
//-----------------------------------------------------------------------------------------
// 1. Ready to receive new command, starts the process by reading Header
//-----------------------------------------------------------------------------------------
if (client.mPendingRcv.IsReady()) {
client.mCmdPendingRead = CmdPendingRead();
client.mPendingRcv.pCommand = &client.mCmdPendingRead;
client.mPendingRcv.bAutoFree = false;
}
//-----------------------------------------------------------------------------------------
// 2. Read incoming command from server
//-----------------------------------------------------------------------------------------
if (client.mPendingRcv.IsPending()) {
Network::DataReceive(client.mpSocketComs, client.mPendingRcv);
// Detected a new command bigger than header, allocate memory for it
if (client.mPendingRcv.pCommand->mSize > sizeof(CmdPendingRead) &&
client.mPendingRcv.pCommand == &client.mCmdPendingRead) {
CmdPendingRead* pCmdHeader = reinterpret_cast<CmdPendingRead*>(
netImguiSizedNew<uint8_t>(client.mPendingRcv.pCommand->mSize));
*pCmdHeader = client.mCmdPendingRead;
client.mPendingRcv.pCommand = pCmdHeader;
client.mPendingRcv.bAutoFree = true;
}
}
//-----------------------------------------------------------------------------------------
// 3. Command fully received from Server, process it
//-----------------------------------------------------------------------------------------
if (client.mPendingRcv.IsDone()) {
if (!client.mPendingRcv.IsError()) {
switch (client.mPendingRcv.pCommand->mType) {
case CmdHeader::eCommands::Input:
Communications_Incoming_Input(client);
break;
case CmdHeader::eCommands::Clipboard:
Communications_Incoming_Clipboard(client);
break;
// Commands not received in main loop, by Client
case CmdHeader::eCommands::Version:
case CmdHeader::eCommands::Texture:
case CmdHeader::eCommands::DrawFrame:
case CmdHeader::eCommands::Background:
case CmdHeader::eCommands::Count:
break;
}
}
// Cleanup after read completion
if (client.mPendingRcv.IsError()) {
// Routine on any disconnect (server restart, browser handover), so
// logged as Info rather than Error.
VLOG(1,
"Communications_Incoming: receive error, triggering disconnect");
client.mbDisconnectPending = true;
}
if (client.mPendingRcv.bAutoFree) {
netImguiDeleteSafe(client.mPendingRcv.pCommand);
}
client.mPendingRcv = PendingCom();
}
}
// Prevent high CPU usage when waiting for new data
else {
// std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::microseconds(250));
}
}
//=================================================================================================
// OUTGOING COMMUNICATIONS
//=================================================================================================
void Communications_Outgoing(ClientInfo& client) {
//---------------------------------------------------------------------------------------------
// Try finishing sending a pending command to Server
//---------------------------------------------------------------------------------------------
if (client.mPendingSend.IsPending()) {
Network::DataSend(client.mpSocketComs, client.mPendingSend);
// Free allocated memory for command
if (client.mPendingSend.IsDone()) {
VLOG(2, "Outgoing: send DONE type=%d, size=%u, error=%s",
static_cast<int>(client.mPendingSend.pCommand->mType),
client.mPendingSend.pCommand->mSize,
client.mPendingSend.IsError() ? "Y" : "N");
client.mPendingSend.pCommand->mSent = true;
if (client.mPendingSend.IsError()) {
client.mbDisconnectPending = true;
}
if (client.mPendingSend.bAutoFree) {
netImguiDeleteSafe(client.mPendingSend.pCommand);
}
client.mPendingSend = PendingCom();
}
}
//---------------------------------------------------------------------------------------------
// Initiate sending next command to Server, when none are in flight
// Note: The cmd order is important, textures must always be sent before
// DrawFrame
// @sammyfreg todo Could add a frame number awareness to avoid sending only
// textures with no stop
//---------------------------------------------------------------------------------------------
constexpr CmdHeader::eCommands kCommandsOrder[] = {
CmdHeader::eCommands::Texture, CmdHeader::eCommands::Background,
CmdHeader::eCommands::Clipboard, CmdHeader::eCommands::DrawFrame};
constexpr uint32_t kCommandCounts = IM_ARRAYSIZE(kCommandsOrder);
uint32_t Index(0);
while (client.mPendingSend.IsReady() && Index < kCommandCounts) {
CmdHeader::eCommands NextCmd = kCommandsOrder[Index++];
switch (NextCmd) {
case CmdHeader::eCommands::Texture:
Communications_Outgoing_Textures(client);
break;
case CmdHeader::eCommands::Background:
Communications_Outgoing_Background(client);
break;
case CmdHeader::eCommands::Clipboard:
Communications_Outgoing_Clipboard(client);
break;
case CmdHeader::eCommands::DrawFrame:
Communications_Outgoing_Frame(client);
break;
// Commands not sent in main loop, by Client
case CmdHeader::eCommands::Input:
case CmdHeader::eCommands::Version:
case CmdHeader::eCommands::Count:
break;
}
}
// Log new send if one was started
VLOG(2, "Outgoing: ready=%s, pending=%s, frameOut=%s",
client.mPendingSend.IsReady() ? "Y" : "N",
client.mPendingSend.IsPending() ? "Y" : "N",
client.mPendingFrameOut.IsNull() ? "empty" : "HAS_DATA");
if (client.mPendingSend.IsPending()) {
VLOG(2, "Outgoing: started type=%d, size=%u",
static_cast<int>(client.mPendingSend.pCommand->mType),
client.mPendingSend.pCommand->mSize);
}
}
//=================================================================================================
// COMMUNICATIONS INITIALIZE
// Initialize a new connection to a RemoteImgui server
//=================================================================================================
bool Communications_Initialize(ClientInfo& client) {
CmdVersion cmdVersionSend, cmdVersionRcv;
PendingCom PendingRcv, PendingSend;
client.mbComInitActive = true;
//---------------------------------------------------------------------
// Handshake confirming connection validity
//---------------------------------------------------------------------
PendingRcv.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionRcv);
while (!PendingRcv.IsDone() &&
cmdVersionRcv.mType == CmdHeader::eCommands::Version) {
auto start_time = std::chrono::steady_clock::now();
while (!client.mbDisconnectPending &&
!Network::DataReceivePending(client.mpSocketPending)) {
std::this_thread::sleep_for(std::chrono::milliseconds(
1)); // Idle until we receive the remote data
if (std::chrono::steady_clock::now() - start_time >
std::chrono::seconds(5)) {
LOG(Error, "Communications_Initialize: Timeout waiting for data");
client.mbDisconnectPending = true;
break;
}
}
if (client.mbDisconnectPending) break;
Network::DataReceive(client.mpSocketPending, PendingRcv);
}
bool bForceConnect =
client.mServerForceConnectEnabled &&
(cmdVersionRcv.mFlags &
static_cast<uint8_t>(CmdVersion::eFlags::ConnectForce)) != 0;
bool bCanConnect = !PendingRcv.IsError() &&
cmdVersionRcv.mType == cmdVersionSend.mType &&
cmdVersionRcv.mVersion == cmdVersionSend.mVersion &&
cmdVersionRcv.mWCharSize == cmdVersionSend.mWCharSize &&
(!client.IsConnected() || bForceConnect);
StringCopy(cmdVersionSend.mClientName, client.mName);
cmdVersionSend.mFlags =
client.IsConnected() && !bCanConnect
? static_cast<uint8_t>(CmdVersion::eFlags::IsConnected)
: 0;
cmdVersionSend.mFlags |=
client.IsConnected() && !client.mServerForceConnectEnabled
? static_cast<uint8_t>(CmdVersion::eFlags::IsUnavailable)
: 0;
PendingSend.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionSend);
while (!PendingSend.IsDone()) {
Network::DataSend(client.mpSocketPending, PendingSend);
}
//---------------------------------------------------------------------
// Connection established, init client
//---------------------------------------------------------------------
if (bCanConnect && !PendingSend.IsError() &&
(!client.IsConnected() || bForceConnect)) {
Network::SocketInfo* pNewComSocket =
client.mpSocketPending.exchange(nullptr);
// If we detect an active connection with Server and 'ForceConnect was
// requested, close it first
if (client.IsConnected()) {
client.mbDisconnectPending = true;
while (client.IsConnected());
}
client.mpSocketComs = pNewComSocket; // Take ownerhip of socket
client.mBGSettingSent.mTextureId =
client.mBGSetting.mTextureId -
1u; // Force sending the Background settings (by making different than
// current settings)
client.mFrameIndex = 0;
client.mClientTextureIDNext = 0;
client.mServerForceConnectEnabled =
(cmdVersionRcv.mFlags &
static_cast<uint8_t>(CmdVersion::eFlags::ConnectExclusive)) == 0;
client.mPendingRcv = PendingCom();
client.mPendingSend = PendingCom();
}
// Disconnect pending socket if init failed
Network::SocketInfo* SocketPending = client.mpSocketPending.exchange(nullptr);
bool bValidConnection = SocketPending == nullptr;
if (SocketPending) {
NetImgui::Internal::Network::Disconnect(SocketPending);
}
client.mbComInitActive = false;
return bValidConnection;
}
//=================================================================================================
// COMMUNICATIONS MAIN LOOP
//=================================================================================================
void Communications_Loop(void* pClientVoid) {
IM_ASSERT(pClientVoid != nullptr);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
pClient->mbDisconnectPending = false;
pClient->mbClientThreadActive = true;
VLOG(1, "Communications_Loop: entering loop");
while (!pClient->mbDisconnectPending) {
Communications_Outgoing(*pClient);
Communications_Incoming(*pClient);
VLOG(2, "Communications_Loop spin, disconnectPending=%s",
pClient->mbDisconnectPending ? "Y" : "N");
}
VLOG(1, "Communications_Loop: exiting loop");
Network::SocketInfo* pSocket = pClient->mpSocketComs.exchange(nullptr);
if (pSocket) {
NetImgui::Internal::Network::Disconnect(pSocket);
}
pClient->mbClientThreadActive = false;
}
//=================================================================================================
// COMMUNICATIONS CONNECT THREAD : Reach out and connect to a NetImGuiServer
//=================================================================================================
void CommunicationsConnect(void* pClientVoid) {
IM_ASSERT(pClientVoid != nullptr);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
if (Communications_Initialize(*pClient)) {
Communications_Loop(pClientVoid);
}
}
//=================================================================================================
// COMMUNICATIONS HOST THREAD : Waiting NetImGuiServer reaching out to us.
// Launch a new com
//loop when connection is established
//=================================================================================================
void CommunicationsHost(void* pClientVoid) {
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
pClient->mbListenThreadActive = true;
pClient->mbDisconnectListen = false;
pClient->mpSocketListen = pClient->mpSocketPending.exchange(nullptr);
while (pClient->mpSocketListen.load() != nullptr &&
!pClient->mbDisconnectListen) {
pClient->mpSocketPending = Network::ListenConnect(pClient->mpSocketListen);
if (pClient->mpSocketPending.load() != nullptr &&
Communications_Initialize(*pClient)) {
pClient->mThreadFunction(Client::Communications_Loop, pClient);
}
std::this_thread::sleep_for(std::chrono::milliseconds(
16)); // Prevents this thread from taking entire core, waiting on
// server connection requests
}
Network::SocketInfo* pSocket = pClient->mpSocketListen.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(pSocket);
pClient->mbListenThreadActive = false;
}
//=================================================================================================
// Support of the Dear ImGui hooks
// (automatic call to NetImgui::BeginFrame()/EndFrame() on
// ImGui::BeginFrame()/Imgui::Render()
//=================================================================================================
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
void HookBeginFrame(ImGuiContext*, ImGuiContextHook* hook) {
Client::ClientInfo& client =
*reinterpret_cast<Client::ClientInfo*>(hook->UserData);
if (!client.mbInsideNewEnd) {
ScopedBool scopedInside(client.mbInsideHook, true);
NetImgui::NewFrame(false);
}
}
void HookEndFrame(ImGuiContext*, ImGuiContextHook* hook) {
Client::ClientInfo& client =
*reinterpret_cast<Client::ClientInfo*>(hook->UserData);
if (!client.mbInsideNewEnd) {
ScopedBool scopedInside(client.mbInsideHook, true);
NetImgui::EndFrame();
}
}
#endif // NETIMGUI_IMGUI_CALLBACK_ENABLED
//=================================================================================================
// CLIENT INFO Constructor
//=================================================================================================
ClientInfo::ClientInfo()
: mpSocketPending(nullptr),
mpSocketComs(nullptr),
mpSocketListen(nullptr),
mbClientThreadActive(false),
mbListenThreadActive(false),
mbComInitActive(false) {}
//=================================================================================================
// CLIENT INFO Constructor
//=================================================================================================
ClientInfo::~ClientInfo() {
ContextRemoveHooks();
// Free all tracked textures
for (auto cmdTexture : mTrackedTextures) {
netImguiDelete(cmdTexture);
}
mTrackedTextures.clear();
netImguiDeleteSafe(mpCmdInputPending);
netImguiDeleteSafe(mpCmdDrawLast);
netImguiDeleteSafe(mpCmdClipboard);
}
//=================================================================================================
// Initialize the associated ImguiContext
//=================================================================================================
void ClientInfo::ContextInitialize() {
mpContext = ImGui::GetCurrentContext();
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
ImGuiContextHook hookNewframe, hookEndframe;
ContextRemoveHooks();
hookNewframe.HookId = 0;
hookNewframe.Type = ImGuiContextHookType_NewFramePre;
hookNewframe.Callback = HookBeginFrame;
hookNewframe.UserData = this;
mhImguiHookNewframe = ImGui::AddContextHook(mpContext, &hookNewframe);
hookEndframe.HookId = 0;
hookEndframe.Type = ImGuiContextHookType_RenderPost;
hookEndframe.Callback = HookEndFrame;
hookEndframe.UserData = this;
mhImguiHookEndframe = ImGui::AddContextHook(mpContext, &hookEndframe);
#endif
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
mbFontUploaded = false;
#endif
}
//=================================================================================================
// Take over a Dear ImGui context for use with NetImgui
//=================================================================================================
void ClientInfo::ContextOverride() {
ScopedImguiContext scopedSourceCtx(mpContext);
// Keep a copy of original settings of this context
mSavedContextValues.Save(mpContext);
mLastOutgoingDrawCheckTime = std::chrono::steady_clock::now();
// Override some settings
// Note: Make sure every setting overwritten here, are handled in
// 'SavedImguiContext::Save(...)'
{
ImGuiIO& newIO = ImGui::GetIO();
newIO.MouseDrawCursor = false;
newIO.BackendPlatformName = "NetImgui";
newIO.BackendRendererName = "DirectX11";
#if IMGUI_VERSION_NUM < 18700
for (uint32_t i(0); i < ImGuiKey_COUNT; ++i) {
newIO.KeyMap[i] = i;
}
#endif
#if IMGUI_VERSION_NUM < 19110
newIO.GetClipboardTextFn = GetClipboardTextFn_NetImguiImpl;
newIO.SetClipboardTextFn = SetClipboardTextFn_NetImguiImpl;
newIO.ClipboardUserData = this;
#else
ImGuiPlatformIO& platformIO = ImGui::GetPlatformIO();
platformIO.Platform_GetClipboardTextFn = GetClipboardTextFn_NetImguiImpl;
platformIO.Platform_SetClipboardTextFn = SetClipboardTextFn_NetImguiImpl;
platformIO.Platform_ClipboardUserData = this;
#endif
#if defined(IMGUI_HAS_VIEWPORT)
newIO.ConfigFlags &=
~(ImGuiConfigFlags_ViewportsEnable); // Viewport unsupported at the
// moment
#endif
// Resend every tracked textures
for (auto textureCmd : mTrackedTextures) {
if (textureCmd->mStatus == CmdTexture::eType::Create) {
TexturePendingServerAdd(*textureCmd);
}
}
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
mFontSavedScaling = ImGui::GetStyle().FontScaleDpi;
newIO.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;
TextureTrackingUpdate(
true); // Force resend all Dear Imgui managed textures
#else
if (mFontCreationFunction != nullptr) {
newIO.FontGlobalScale = 1;
mFontServerScale = -1;
}
#endif
}
}
//=================================================================================================
// Restore a Dear ImGui context to initial state before we modified it
//=================================================================================================
void ClientInfo::ContextRestore() {
// Note: only happens if context overriden is same as current one, to prevent
// trying to restore to a deleted context
if (IsContextOverriden() && ImGui::GetCurrentContext() == mpContext) {
#ifdef IMGUI_HAS_VIEWPORT
ImGui::UpdatePlatformWindows(); // Prevents issue with mismatched frame
// tracking, when restoring enabled
// viewport feature
#endif
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
// Remove all auto managed texture from our tracking,
// leaving only the manually managed textures.
// Auto-Managed will automatically get re-added on reconnect.
ImVector<ImTextureData*>& Textures = ImGui::GetPlatformIO().Textures;
ImTextureRef ClientTextureRef;
for (auto TexData : Textures) {
ImTextureStatus TexStatus =
TexData != nullptr ? TexData->Status : ImTextureStatus_Destroyed;
if (TexStatus != ImTextureStatus_Destroyed &&
TexStatus != ImTextureStatus_WantDestroy) {
ClientTextureRef._TexData = TexData;
TextureTrackingRem(ClientTextureRef.GetTexID());
}
}
ImGui::GetStyle().FontScaleDpi = mFontSavedScaling;
#else
if (mFontCreationFunction && ImGui::GetIO().Fonts &&
ImGui::GetIO().Fonts->Fonts.size() > 0) {
float noScaleSize =
ImGui::GetIO().Fonts->Fonts[0]->FontSize / mFontServerScale;
float originalScale =
mSavedContextValues.mFontGeneratedSize / noScaleSize;
mFontCreationFunction(mFontSavedScaling, originalScale);
}
#endif
mSavedContextValues.Restore(mpContext);
}
}
//=================================================================================================
// Remove callback hooks, once we detect a disconnection
//=================================================================================================
void ClientInfo::ContextRemoveHooks() {
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
if (mpContext && mhImguiHookNewframe != 0) {
ImGui::RemoveContextHook(mpContext, mhImguiHookNewframe);
ImGui::RemoveContextHook(mpContext, mhImguiHookEndframe);
mhImguiHookNewframe = mhImguiHookNewframe = 0;
}
#endif
}
//=================================================================================================
// If backend doesn't support Dear ImGui managed texture (used by font) as
// indicated with 'ImGuiBackendFlags_RendererHasTextures', we still want to
// handle it with our RemoteServer so we take it upon ourselves to clear the
// list of texture updates. Otherwise, we'd be constantly re applying the same
// updates
//=================================================================================================
void ClientInfo::TextureTrackingClear() {
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
if (IsConnected() && (mSavedContextValues.mBackendFlags &
ImGuiBackendFlags_RendererHasTextures) == 0) {
// This is basically a stripped version of 'ImGui_ImplDX11_UpdateTexture'
// where we only care about the TexID and status values
ImVector<ImTextureData*>& Textures = ImGui::GetPlatformIO().Textures;
for (auto TexData : Textures) {
if (TexData->Status == ImTextureStatus_WantCreate) {
IM_ASSERT(TexData->TexID == ImTextureID_Invalid &&
TexData->BackendUserData == nullptr);
static ImTextureID sUniqueID(1);
TexData->SetTexID(static_cast<ImTextureID>(sUniqueID++));
TexData->SetStatus(ImTextureStatus_OK);
} else if (TexData->Status == ImTextureStatus_WantUpdates) {
TexData->SetStatus(ImTextureStatus_OK);
} else if (TexData->Status == ImTextureStatus_WantDestroy &&
TexData->UnusedFrames > 0) {
TexData->SetTexID(ImTextureID_Invalid);
TexData->SetStatus(ImTextureStatus_Destroyed);
}
}
}
// Remove all Dear ImGui Managed textures on disconnect,
// they will be resent on reconnect
if (!IsConnected() && mDearImguiTextureCount > 0) {
for (auto pCmdTexture : mTrackedTextures) {
if (pCmdTexture->mIsDearImGuiManaged) {
mbTrackedTexturesPending |=
TextureTrackingRem(pCmdTexture->mTextureClientID);
}
}
}
#endif
}
//=================================================================================================
// Process backend texture updates and forward it to NetImguiServer
// Note: This is mostly used by the Font Atlas Texture added in Dear ImGui 1.92+
//=================================================================================================
void ClientInfo::TextureTrackingUpdate(bool bResendAll) {
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
if (IsConnected()) {
ImVector<ImTextureData*>& Textures = ImGui::GetPlatformIO().Textures;
ImTextureRef ClientTextureRef;
//------------------------------------------------------------------------
// Find and send all Dear ImGui managed textures creation/update to
// remote NetImgui Server
//------------------------------------------------------------------------
for (auto TexData : Textures) {
ClientTextureRef._TexData = TexData;
ImTextureStatus TexStatus =
TexData != nullptr ? TexData->Status : ImTextureStatus_OK;
eTexFormat TexFormat =
NetImgui::Internal::ConvertTextureFormat(TexData->Format);
uint32_t dataSize = 0;
uint16_t w = static_cast<uint16_t>(TexData->Width);
uint16_t h = static_cast<uint16_t>(TexData->Height);
if ((TexStatus == ImTextureStatus_WantCreate) ||
(bResendAll && TexStatus != ImTextureStatus_Destroyed &&
TexStatus != ImTextureStatus_WantDestroy)) {
// @sammyfreg todo UserID and mClientTextureIDNext used for dear
// imgui managed's textures
// could potentially collide when
//generating ClientTextureID. Needs something more robust.
TexData->UniqueID = ++mClientTextureIDNext;
CmdTexture* pCmdTexture = TextureCmdAllocate(
ConvertToClientTexID(ClientTextureRef), w, h, TexFormat, dataSize);
if (pCmdTexture) {
memcpy(pCmdTexture->mpTextureData.Get(), TexData->GetPixels(),
dataSize);
pCmdTexture->mUpdatable = true;
pCmdTexture->mpTextureData.ToOffset();
pCmdTexture->mIsDearImGuiManaged = true;
TextureTrackingAdd(
*pCmdTexture); // Add texture to our list and request it to be
// sent over to Server
mbTrackedTexturesPending = true;
}
} else if (TexStatus == ImTextureStatus_WantUpdates) {
uint16_t dstW = static_cast<uint16_t>(TexData->UpdateRect.w);
uint16_t dstH = static_cast<uint16_t>(TexData->UpdateRect.h);
CmdTexture* pCmdTexture =
TextureCmdAllocate(ConvertToClientTexID(ClientTextureRef), dstW,
dstH, TexFormat, dataSize);
if (pCmdTexture) {
size_t SrcLineBytes = static_cast<size_t>(TexData->GetPitch());
size_t DstLineBytes = static_cast<size_t>(TexData->BytesPerPixel *
TexData->UpdateRect.w);
size_t OffsetBytes = static_cast<size_t>(
TexData->GetPitch() * TexData->UpdateRect.y +
TexData->BytesPerPixel * TexData->UpdateRect.x);
const uint8_t* pDataSrc =
reinterpret_cast<uint8_t*>(TexData->GetPixels());
uint8_t* pDataDst = pCmdTexture->mpTextureData.Get();
for (uint64_t y(0); y < TexData->UpdateRect.h; ++y) {
memcpy(&pDataDst[y * DstLineBytes],
&pDataSrc[OffsetBytes + (y * SrcLineBytes)], DstLineBytes);
}
pCmdTexture->mStatus = CmdTexture::eType::Update;
pCmdTexture->mOffsetX = TexData->UpdateRect.x;
pCmdTexture->mOffsetY = TexData->UpdateRect.y;
pCmdTexture->mUpdatable = true;
pCmdTexture->mIsDearImGuiManaged = true;
pCmdTexture->mpTextureData.ToOffset();
TexturePendingServerAdd(
*pCmdTexture); // Request texture to be sent over to Server
mbTrackedTexturesPending = true;
}
}
}
//------------------------------------------------------------------------
// Detects Textures changes that have not been tracked
// (caused by internal font atlas changes)
// This is not ideal with large texture count but should be ok for most
// cases. Using a unordered_map for 'mTrackedTextures' could help for
// high count, but adds a std dependency
//------------------------------------------------------------------------
if (mDearImguiTextureCount != Textures.Size) {
for (auto pCmdTexture : mTrackedTextures) {
if (pCmdTexture->mIsDearImGuiManaged) {
bool bFound(false);
for (int i(0); !bFound && i < Textures.Size; ++i) {
ClientTextureRef._TexData = Textures[i];
bFound = pCmdTexture->mTextureClientID ==
ConvertToClientTexID(ClientTextureRef);
}
if (!bFound) {
mbTrackedTexturesPending |=
TextureTrackingRem(pCmdTexture->mTextureClientID);
}
}
}
}
}
#endif
//----------------------------------------
if (mbTrackedTexturesPending) {
int TrackedCount(mTrackedTextures.Size);
mbTrackedTexturesPending = false;
for (int i(0); i < TrackedCount; ++i) {
mbTrackedTexturesPending |=
!mTrackedTextures[i]->mSent &&
mTrackedTextures[i]->mStatus != CmdTexture::eType::Create;
// As soon as we detect a command to be un-needed, release it
if (mTrackedTextures[i]->mSent &&
mTrackedTextures[i]->mStatus != CmdTexture::eType::Create) {
netImguiDelete(mTrackedTextures[i]);
mTrackedTextures[i] =
mTrackedTextures[--TrackedCount]; // Erase swap with last element
--i; // re-process same index after its entry was swapped with last
// valid element
}
}
mTrackedTextures.resize(TrackedCount);
}
}
CmdTexture* ClientInfo::TextureCmdAllocate(ClientTextureID clientTexID,
uint16_t width, uint16_t height,
eTexFormat format,
uint32_t& dataSizeInOut) {
if (format != eTexFormat::kTexFmtCustom) {
dataSizeInOut = GetTexture_BytePerImage(format, width, height);
}
uint32_t SizeNeeded = dataSizeInOut + sizeof(CmdTexture);
CmdTexture* pCmdTexture = netImguiSizedNew<CmdTexture>(SizeNeeded);
if (pCmdTexture) {
pCmdTexture->mpTextureData.SetPtr(
reinterpret_cast<uint8_t*>(&pCmdTexture[1]));
pCmdTexture->mStatus = CmdTexture::eType::Create;
pCmdTexture->mSize = SizeNeeded;
pCmdTexture->mWidth = width;
pCmdTexture->mHeight = height;
pCmdTexture->mTextureClientID = clientTexID;
pCmdTexture->mFormat = static_cast<uint8_t>(format);
pCmdTexture->mUpdatable = false;
}
return pCmdTexture;
}
// Start tracking this client texture
bool ClientInfo::TextureTrackingAdd(CmdTexture& cmdTexture) {
if (cmdTexture.mStatus != CmdTexture::eType::Create) {
return false;
}
TextureTrackingRem(cmdTexture.mTextureClientID);
mTrackedTextures.push_back(
&cmdTexture); // Add the new entry needing to be tracked
TexturePendingServerAdd(
cmdTexture); // Request texture to be sent over to Server
mDearImguiTextureCount += cmdTexture.mIsDearImGuiManaged ? 1 : 0;
return true;
}
bool ClientInfo::TextureTrackingRem(ClientTextureID clientTextureID) {
// If texture has been sent to server, re-purpose existing command
// as a 'destroy' and re-send it to server
for (int i(0); i < mTrackedTextures.Size; ++i) {
CmdTexture* pCmdTexture = mTrackedTextures[i];
if (pCmdTexture && pCmdTexture->mTextureClientID == clientTextureID &&
pCmdTexture->mStatus == CmdTexture::eType::Create) {
pCmdTexture->mSent = false;
pCmdTexture->mStatus =
CmdTexture::eType::Destroy; // Re-purpose create cmd to destroy the
// texture
TexturePendingServerAdd(*pCmdTexture);
// Remove item from our list
mDearImguiTextureCount -= pCmdTexture->mIsDearImGuiManaged ? 1 : 0;
mTrackedTextures[i] = mTrackedTextures.back();
mTrackedTextures.pop_back();
return true;
}
}
return false;
}
void ClientInfo::TexturePendingServerAdd(CmdTexture& cmdTexture) {
std::lock_guard<std::mutex> guard(mPendingTexturesLock);
if (IsConnected()) {
// Find last added entry
CmdTexture** ppNextTexture = &mPendingTextures;
CmdTexture* pendingTexture = mPendingTextures;
while (pendingTexture != nullptr) {
// Remove all unprocessed texture commands with same id
// (only need the latest action for create/destroy, but can have multiple
// update queued)
if (cmdTexture.mStatus != CmdTexture::eType::Update &&
cmdTexture.mSent == false &&
cmdTexture.mTextureClientID == pendingTexture->mTextureClientID) {
// Mark as sent and un-needed (which gets it removed from tracking array
// and deleted later)
pendingTexture->mSent = true;
pendingTexture->mStatus = CmdTexture::eType::Destroy;
*ppNextTexture = pendingTexture->mpNext;
}
ppNextTexture = &pendingTexture->mpNext;
pendingTexture = pendingTexture->mpNext;
}
// Add as last element and ready to be sent
cmdTexture.mSent = false;
cmdTexture.mpNext = nullptr;
*ppNextTexture = &cmdTexture;
}
}
//=================================================================================================
// Create a new Draw Command from Dear Imgui draw data.
// 1. New ImGui frame has been completed, create a new draw command from draw
// data (Main Thread)
// 2. We see a pending Draw Command, take ownership of it and send it to Server
// (Com thread)
//=================================================================================================
void ClientInfo::ProcessDrawData(const ImDrawData* pDearImguiData,
ImGuiMouseCursor mouseCursor) {
if (!mbValidDrawFrame) return;
VLOG(2, "ProcessDrawData: %s (mbValidDrawFrame=%s, mDesiredFps=%.1f)",
mbValidDrawFrame ? "SENDING" : "SKIPPED",
mbValidDrawFrame ? "true" : "false", mDesiredFps);
CmdDrawFrame* pDrawFrameNew =
ConvertToCmdDrawFrame(pDearImguiData, mouseCursor);
pDrawFrameNew->mCompressed =
mClientCompressionMode == eCompressionMode::kForceEnable ||
(mClientCompressionMode == eCompressionMode::kUseServerSetting &&
mServerCompressionEnabled);
mPendingFrameOut.Assign(pDrawFrameNew);
}
} // namespace Client
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_WarningReenable.h"
#endif // #if NETIMGUI_ENABLED
b47e70849This file is pinned to the selected release. Download the original to open it in your design tool.