Export netimgui files in MuJoCo Copybara configuration for use with the upcoming Web Viewer

PiperOrigin-RevId: 954769526
Change-Id: I4cdee083082f7d55c2babd5e664ec0283a2aaf63
This commit is contained in:
Matija Kecman
2026-07-27 12:31:15 -07:00
committed by Copybara-Service
parent c7b6e0b8dd
commit ca5337161d
40 changed files with 15483 additions and 0 deletions
@@ -0,0 +1,417 @@
#include "NetImguiServer_App.h"
#include "Fonts/Roboto_Medium.cpp"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_Network.h"
#include "NetImguiServer_RemoteClient.h"
#include "NetImguiServer_UI.h"
namespace NetImguiServer {
namespace App {
constexpr uint32_t kClientCountMax =
32; //! @sammyfreg todo: support unlimited client count
ImVector<ServerTexture*>
gServerTextures; // List of ALL server created textures (used by server and
// clients)
ServerTexture* gServerTextureEmpty =
nullptr; // Empty texture used when no valid texture found
bool gLoadedConfigOnce = false;
void UpdateServerTextures();
bool Startup(const char* CmdLine) {
//---------------------------------------------------------------------------------------------
// Load Settings savefile and parse for auto connect commandline option
//---------------------------------------------------------------------------------------------
if (!gLoadedConfigOnce) {
NetImguiServer::Config::Client::LoadAll();
gLoadedConfigOnce = true;
}
AddTransientClientConfigFromString(CmdLine);
//---------------------------------------------------------------------------------------------
// Perform application initialization:
//---------------------------------------------------------------------------------------------
if (RemoteClient::Client::Startup(kClientCountMax) &&
NetImguiServer::Network::Startup() && NetImguiServer::UI::Startup()) {
NetImgui::Internal::CmdTexture cmdTexture;
uint32_t EmptyData[4 * 4] = {
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF,
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF,
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF};
cmdTexture.mTextureClientID = 0;
cmdTexture.mFormat = ImTextureFormat::ImTextureFormat_RGBA32;
cmdTexture.mWidth = cmdTexture.mHeight = 4;
cmdTexture.mpTextureData.SetPtr((uint8_t*)EmptyData);
gServerTextureEmpty = CreateTexture(cmdTexture, sizeof(EmptyData));
LoadFonts();
ImGui::GetIO().IniFilename =
nullptr; // Disable server ImGui ini settings (not really needed, and
// avoid imgui.ini filename conflicts)
ImGui::GetIO().LogFilename = nullptr;
return HAL_Startup(CmdLine);
}
return false;
}
void Shutdown() {
// Mark all texture resources as wanting deletion
for (ServerTexture* texServer : gServerTextures) {
if (texServer && texServer->IsValid()) {
if (!texServer->mIsCustom) {
texServer->mTexData.SetStatus(ImTextureStatus_WantDestroy);
texServer->mTexData.UnusedFrames = 1;
} else {
#if TEXTURE_CUSTOM_SAMPLE
if (texServer && texServer->mIsCustom) {
0 // TODO
}
#endif
}
}
}
// Allow Dear ImGui to delete the textures
ImGui::NewFrame();
ImGui::Render();
HAL_RenderDrawData(ImGui::GetDrawData());
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
// Finish removing them from Dear ImGui user textures and delete objects
UpdateServerTextures();
// Update Dear ImGui texture list (so it doesn't have deleted items in it)
ImGui::NewFrame();
ImGui::Render();
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
// Remove deleted textures from clients
for (uint32_t i(0); i < RemoteClient::Client::GetCountMax(); ++i) {
RemoteClient::Client& client = RemoteClient::Client::Get(i);
client.mTextureTable.clear();
}
NetImguiServer::Network::Shutdown();
NetImguiServer::UI::Shutdown();
NetImguiServer::Config::Client::Clear();
RemoteClient::Client::Shutdown();
HAL_Shutdown();
}
//=================================================================================================
// INIT CLIENT CONFIG FROM STRING
// Take a commandline string, and create a ClientConfig from it.
// Simple format of (Hostname);(HostPort)
bool AddTransientClientConfigFromString(const char* string)
//=================================================================================================
{
NetImguiServer::Config::Client cmdlineClient;
const char* zEntryStart = string;
const char* zEntryCur = string;
int paramIndex = 0;
cmdlineClient.mConfigType =
NetImguiServer::Config::Client::eConfigType::Transient;
NetImgui::Internal::StringCopy(cmdlineClient.mClientName, "Commandline");
while (*zEntryCur != 0) {
zEntryCur++;
// Skip commandline preamble holding path to executable
if (*zEntryCur == ' ' && *(zEntryCur + 1) != 0) {
zEntryStart = zEntryCur + 1;
}
if ((*zEntryCur == ';' || *zEntryCur == 0)) {
if (paramIndex == 0)
NetImgui::Internal::StringCopy(cmdlineClient.mHostName, zEntryStart,
zEntryCur - zEntryStart);
else if (paramIndex == 1)
cmdlineClient.mHostPort = static_cast<uint32_t>(atoi(zEntryStart));
cmdlineClient.mConnectAuto =
paramIndex >=
1; // Mark valid for connexion as soon as we have a HostAddress
zEntryStart = zEntryCur + 1;
paramIndex++;
}
}
if (cmdlineClient.mConnectAuto) {
NetImguiServer::Config::Client::SetConfig(cmdlineClient);
return true;
}
return false;
}
//=================================================================================================
// DRAW CLIENT BACKGROUND
// Create a separate Dear ImGui drawing context, to generate a commandlist that
// will fill the RenderTarget with a specific background picture
void DrawClientBackground(RemoteClient::Client& client)
//=================================================================================================
{
NetImgui::Internal::CmdBackground* pBGUpdate =
client.mPendingBackgroundIn.Release();
if (pBGUpdate) {
client.mBGSettings = *pBGUpdate;
client.mBGNeedUpdate = true;
netImguiDeleteSafe(pBGUpdate);
}
if (client.mpBGContext == nullptr) {
client.mpBGContext = ImGui::CreateContext(ImGui::GetIO().Fonts);
client.mpBGContext->IO.DeltaTime = 1 / 30.f;
client.mpBGContext->IO.IniFilename =
nullptr; // Disable server ImGui ini settings (not really needed, and
// avoid imgui.ini filename conflicts)
client.mpBGContext->IO.LogFilename = nullptr;
client.mpBGContext->IO.BackendFlags |=
ImGuiBackendFlags_RendererHasTextures;
}
// Detect when the main font textures was updated,
// in which case we need to re-generate the BG DrawData
const ImVector<ImTextureData*>& MainTextures =
ImGui::GetPlatformIO().Textures;
NetImgui::Internal::ScopedImguiContext scopedCtx(client.mpBGContext);
const ImVector<ImTextureData*>& BGTextures = ImGui::GetPlatformIO().Textures;
for (ImTextureData* texData : BGTextures) {
client.mBGNeedUpdate |= MainTextures.contains(texData) == false;
}
// Update the BG DrawData
// Note: Maybe we should try rendering it directly in client windows every
// frame instead, to keep things simpler
if (client.mBGNeedUpdate) {
ImGui::GetIO().DisplaySize = ImVec2(client.mAreaSizeX, client.mAreaSizeY);
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(client.mAreaSizeX, client.mAreaSizeY));
ImGui::Begin("Background", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoSavedSettings);
// Look for the desired texture (and use default if not found)
auto texIt = client.mTextureTable.find(client.mBGSettings.mTextureId);
const ServerTexture* pTexture = texIt == client.mTextureTable.end()
? UI::GetBackgroundTexture()
: texIt->second;
UI::DrawCenteredBackground(pTexture,
ImVec4(client.mBGSettings.mTextureTint[0],
client.mBGSettings.mTextureTint[1],
client.mBGSettings.mTextureTint[2],
client.mBGSettings.mTextureTint[3]));
ImGui::End();
ImGui::Render();
client.mBGNeedUpdate = false;
}
}
//=================================================================================================
void UpdateServerTextures()
//=================================================================================================
{
for (int i(gServerTextures.size() - 1); i >= 0; --i) {
if (gServerTextures[i]) {
ServerTexture& ServerTex = *gServerTextures[i];
// Release un-needed pixel data once it has been processed by backend
if (ServerTex.mIsUpdatable == false &&
ServerTex.mTexData.Pixels != nullptr &&
ServerTex.mTexData.Status == ImTextureStatus_OK) {
ServerTex.mTexData.DestroyPixels();
}
// Backend deleted the texture, remove it from our list
else if (ServerTex.mTexData.Status == ImTextureStatus_Destroyed) {
ImGui::UnregisterUserTexture(&ServerTex.mTexData);
delete gServerTextures[i];
gServerTextures[i] = nullptr;
}
// Send deletion request to backend
else if (ServerTex.mTexData.WantDestroyNextFrame) {
const RemoteClient::Client* Client =
ServerTex.mOwnerClientIndex >= 0
? &RemoteClient::Client::Get(ServerTex.mOwnerClientIndex)
: nullptr;
if (!Client || !Client->mpImguiDrawData ||
Client->mpImguiDrawData->mFrameIndex > ServerTex.mLastFrameUsed) {
if (ServerTex.mTexData.UnusedFrames++ > 0) {
ServerTex.mTexData.Status = ImTextureStatus_WantDestroy;
}
}
}
}
// Remove release textures (null) from our list
if (gServerTextures[i] == nullptr) {
ImSwap(gServerTextures[i], gServerTextures[gServerTextures.Size - 1]);
gServerTextures.pop_back();
}
}
}
//=================================================================================================
// Default texture creation behavior, relying on Dear ImGui backend to do
// the heavy lifting of texture creation and management
bool CreateTexture_Default(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize)
//=================================================================================================
{
if (!serverTexture.mIsCustom && cmdTexture.mpTextureData.Get() != nullptr) {
serverTexture.mIsUpdatable = cmdTexture.mUpdatable;
serverTexture.mTexData.Create(ImTextureFormat::ImTextureFormat_RGBA32,
cmdTexture.mWidth, cmdTexture.mHeight);
if (cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_RGBA32) {
memcpy(serverTexture.mTexData.Pixels, cmdTexture.mpTextureData.Get(),
customDataSize);
} else if (cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_Alpha8) {
const uint8_t* pSrcCur = cmdTexture.mpTextureData.Get();
uint32_t* pDestCur =
reinterpret_cast<uint32_t*>(serverTexture.mTexData.GetPixels());
uint32_t* pDestEnd = &pDestCur[cmdTexture.mHeight * cmdTexture.mWidth];
while (pDestCur < pDestEnd) {
*pDestCur++ = 0x00FFFFFF | (uint64_t(*pSrcCur++) << 24);
}
} else {
IM_ASSERT_USER_ERROR(0, "Unsupported format");
}
serverTexture.mTexData.Status = ImTextureStatus_WantCreate;
serverTexture.mTexData.UseColors =
cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_RGBA32;
ImGui::RegisterUserTexture(&serverTexture.mTexData);
return true;
}
return false;
}
//=================================================================================================
ServerTexture* CreateTexture(const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t textureDataSize)
//=================================================================================================
{
ServerTexture* serverTex(new ServerTexture);
if (serverTex) {
serverTex->mIsCustom =
cmdTexture.mFormat == NetImgui::eTexFormat::kTexFmtCustom;
serverTex->mClientTexID = cmdTexture.mTextureClientID;
serverTex->mIsUpdatable = false;
if (CreateTexture_Custom(*serverTex, cmdTexture, textureDataSize) ||
CreateTexture_Default(*serverTex, cmdTexture, textureDataSize)) {
gServerTextures.push_back(serverTex);
} else {
delete serverTex;
serverTex = NULL;
}
}
return serverTex;
}
//=================================================================================================
// Initialize all needed fonts by the NetImguiServer application
void LoadFonts()
//=================================================================================================
{
ImFontConfig fontConfig;
ImFontAtlas* pFontAtlas = ImGui::GetIO().Fonts;
// Add Fonts here...
// Using a different default font (provided with Dear ImGui)
NetImgui::Internal::StringCopy(fontConfig.Name, "Roboto Medium");
if (!pFontAtlas->AddFontFromMemoryCompressedTTF(Roboto_Medium_compressed_data,
Roboto_Medium_compressed_size,
0.f, &fontConfig)) {
pFontAtlas->AddFontDefault(&fontConfig);
}
}
//=================================================================================================
void UpdateWindowPlacement(int x, int y, int w, int h, bool isMaximized)
//=================================================================================================
{
NetImguiServer::Config::Server::sWindowPlacement[0] = x;
NetImguiServer::Config::Server::sWindowPlacement[1] = y;
NetImguiServer::Config::Server::sWindowPlacement[2] = w;
NetImguiServer::Config::Server::sWindowPlacement[3] = h;
NetImguiServer::Config::Server::sWindowMaximized = isMaximized;
NetImguiServer::Config::Client::SaveAll();
}
//=================================================================================================
WindowPlacement GetWindowPlacement()
//=================================================================================================
{
if (!gLoadedConfigOnce) {
NetImguiServer::Config::Client::LoadAll();
gLoadedConfigOnce = true;
}
WindowPlacement wp;
wp.x = NetImguiServer::Config::Server::sWindowPlacement[0];
wp.y = NetImguiServer::Config::Server::sWindowPlacement[1];
wp.w = ImMax(100, NetImguiServer::Config::Server::sWindowPlacement[2]);
wp.h = ImMax(100, NetImguiServer::Config::Server::sWindowPlacement[3]);
wp.isMaximized = NetImguiServer::Config::Server::sWindowMaximized;
return wp;
}
//=================================================================================================
// UPDATE REMOTE CONTENT
// Create a render target for each connected remote client once, and update it
// every frame with the last drawing commands received from it. This Render
// Target will then be used normally as the background image of each client
// window renderered by this Server
void UpdateClientDraw()
//=================================================================================================
{
for (uint32_t i(0); i < RemoteClient::Client::GetCountMax(); ++i) {
RemoteClient::Client& client = RemoteClient::Client::Get(i);
if (client.mbIsConnected) {
if (client.mbIsReleased) {
client.Uninitialize();
} else {
client.ProcessPendingTextureCmds();
if (client.mbIsVisible) {
// Update the RenderTarget destination of each client, of size was
// updated
if (client.mAreaSizeX > 0 && client.mAreaSizeY > 0 &&
(!client.mpHAL_AreaRT ||
client.mAreaRTSizeX != client.mAreaSizeX ||
client.mAreaRTSizeY != client.mAreaSizeY)) {
if (HAL_CreateRenderTarget(client.mAreaSizeX, client.mAreaSizeY,
client.mpHAL_AreaRT,
client.mHAL_AreaTexture)) {
client.mAreaRTSizeX = client.mAreaSizeX;
client.mAreaRTSizeY = client.mAreaSizeY;
client.mLastUpdateTime =
std::chrono::steady_clock::now() -
std::chrono::hours(1); // Will redraw the client
client.mBGNeedUpdate = true;
}
}
// Render the remote results
ImDrawData* pDrawData =
client.GetImguiDrawData(gServerTextureEmpty->mTexData.GetTexID());
if (pDrawData) {
DrawClientBackground(client);
HAL_RenderDrawData(client, pDrawData);
}
}
}
}
}
UpdateServerTextures();
}
} // namespace App
} // namespace NetImguiServer
@@ -0,0 +1,144 @@
// Google modifications:
// - Added #ifndef guards around HAL_API_PLATFORM_* defines so they can be
// overridden from BUILD-level defines (-D flags).
#pragma once
#include <Private/NetImgui_Network.h>
#include <Private/NetImgui_Shared.h>
//=============================================================================================
// SELECT RENDERING/OS API HERE
//=============================================================================================
#ifndef HAL_API_PLATFORM_WIN32_DX11
#define HAL_API_PLATFORM_WIN32_DX11 1
#endif
#ifndef HAL_API_PLATFORM_GLFW_GL3
#define HAL_API_PLATFORM_GLFW_GL3 0 // Note: Currently doesn't work on VS2026
#endif
#ifndef HAL_API_PLATFORM_SOKOL
#define HAL_API_PLATFORM_SOKOL \
0 // Sokol Lib needs to be updated to latest Dear ImGui 1.92 support for this
// to work
#endif
#define HAL_API_RENDERTARGET_INVERT_Y \
(HAL_API_PLATFORM_GLFW_GL3 || \
HAL_API_PLATFORM_SOKOL) // Invert client render target Y axis (since OpenGL
// start texture UV from BottomLeft instead of
// DirectX TopLeft)
//=============================================================================================
// Forward declare
namespace NetImguiServer {
namespace RemoteClient {
struct Client;
}
} // namespace NetImguiServer
namespace NetImgui {
namespace Internal {
struct CmdTexture;
}
} // namespace NetImgui
namespace NetImguiServer {
namespace App {
struct WindowPlacement {
int x = 0;
int y = 0;
int w = 0;
int h = 0;
bool isMaximized = false;
};
//=============================================================================================
// Code specific to 'NetImgui Server' application and needed inside platform
// specific code
//=============================================================================================
// Additional initialisation needed by 'NetImGui Server' and not part of default
// ImGui sample code
bool Startup(const char* CmdLine);
// Prepare for shutdown of application
void Shutdown();
// Receive rendering request of each Remote client and output it to their own
// RenderTarget
void UpdateClientDraw();
// Save server window placement (to restore it on next start)
void UpdateWindowPlacement(int x, int y, int w, int h, bool isMaximized);
// Get last server window placement
WindowPlacement GetWindowPlacement();
// Add a new remote client config to our list (to attempt connexion)
bool AddTransientClientConfigFromString(const char* string);
// Initialize the font atlas used by the Serve
void LoadFonts();
// Descriptor of each textures by Server. Format always RGBA8
struct ServerTexture {
inline ServerTexture() {
mTexData.Status = ImTextureStatus_Destroyed;
mTexData.RefCount = 1;
}
inline bool IsValid() const {
return mTexData.Status != ImTextureStatus_WantCreate &&
mTexData.Status != ImTextureStatus_Destroyed;
}
inline void MarkForDelete() {
mTexData.WantDestroyNextFrame = true;
mTexData.UnusedFrames = 0;
}
ImTextureData mTexData; // Struct used by backend for texture support
ImTextureID mClientTexID; // Client UserID associated with this texture
uint64_t mCustomData = 0u; // Memory available to custom command
uint64_t mLastFrameUsed = 0u; // Last draw frame this texture was used
// (needed for resources release)
int32_t mOwnerClientIndex = -1; // Client that created this texture (if any)
uint8_t mIsCustom = 0u; // Format handled by custom version of NetImguiServer
// modified by library user
uint8_t mIsUpdatable = 0u; // True when textures can be updated (font)
uint8_t mPadding[6] = {};
};
//=============================================================================================
// Handling of texture data
//=============================================================================================
ServerTexture* CreateTexture(const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
// Library users can implement their own texture format (on client/server).
// Useful for vidoe streaming, new format, etc.
bool CreateTexture_Custom(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
bool DestroyTexture_Custom(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
//=============================================================================================
// Note (H)ardware (A)bstraction (L)ayer
// When porting the 'NetImgui Server' application to other
//platform, theses are the few functions needed to be supported by each specific
//API that are not already supported by de 'Dear ImGui' provided backends
//=============================================================================================
// Additional initialisation that are platform specific
bool HAL_Startup(const char* CmdLine);
// Prepare for shutdown of application, with platform specific code
void HAL_Shutdown();
// Receive a platform specific socket, and return us with info on the connection
bool HAL_GetSocketInfo(NetImgui::Internal::Network::SocketInfo* pClientSocket,
char* pOutHostname, size_t HostNameLen, int& outPort);
// Provide the current user setting folder (used to save the shared config file)
const char* HAL_GetUserSettingFolder();
// Return true when new content should be retrieved from Clipboard (avoid
// constantly reading/converting content)
bool HAL_GetClipboardUpdated();
// Receive a ImDrawData drawlist and render it to backbuffer
void HAL_RenderDrawData(ImDrawData* pDrawData);
// Receive a ImDrawData drawlist and request Dear ImGui's backend to output it
// into a texture
void HAL_RenderDrawData(RemoteClient::Client& client, ImDrawData* pDrawData);
// Allocate a RenderTarget that each client will use to output their ImGui
// drawing into.
bool HAL_CreateRenderTarget(uint16_t Width, uint16_t Height, void*& pOutRT,
ImTextureData& OutTexture);
// Free a RenderTarget resource
void HAL_DestroyRenderTarget(void*& pOutRT, ImTextureData& OutTexture);
} // namespace App
} // namespace NetImguiServer
@@ -0,0 +1,441 @@
// Google modifications:
// - Updated nlohmann/json include path for Google third_party layout.
#include "NetImguiServer_Config.h"
#include <NetImgui_Api.h>
#include <fstream>
#include <mutex>
#include <nlohmann/json.hpp>
#include "NetImguiServer_RemoteClient.h"
namespace NetImguiServer {
namespace Config {
static ImVector<Client*> gConfigList;
static std::mutex gConfigLock;
static Client::RuntimeID gRuntimeID = static_cast<Client::RuntimeID>(1);
static constexpr char kConfigField_ServerPort[] = "ServerPort";
static constexpr char kConfigField_ServerRefreshActive[] = "RefreshFPSActive";
static constexpr char kConfigField_ServerRefreshInactive[] =
"RefreshFPSInactive";
static constexpr char kConfigField_ServerDPIScaleRatio[] = "DPIScaleRatio";
static constexpr char kConfigField_ServerCompressionEnable[] =
"CompressionEnable";
static constexpr char kConfigField_ServerFontSize[] = "ServerFontSize";
static constexpr char kConfigField_ServerWindowPlacementX[] =
"ServerWindowPlacementX";
static constexpr char kConfigField_ServerWindowPlacementY[] =
"ServerWindowPlacementY";
static constexpr char kConfigField_ServerWindowPlacementW[] =
"ServerWindowPlacementW";
static constexpr char kConfigField_ServerWindowPlacementH[] =
"ServerWindowPlacementH";
static constexpr char kConfigField_ServerWindowMaximized[] =
"ServerWindowMaximized";
static constexpr char kConfigField_Note[] = "Note";
static constexpr char kConfigField_Version[] = "Version";
static constexpr char kConfigField_Configs[] = "Configs";
static constexpr char kConfigField_Name[] = "Name";
static constexpr char kConfigField_Hostname[] = "Hostname";
static constexpr char kConfigField_Hostport[] = "HostPort";
static constexpr char kConfigField_AutoConnect[] = "Auto";
static constexpr char kConfigField_BlockTakeover[] = "BlockTakeover";
static constexpr char kConfigField_DPIScaleEnabled[] = "DPIScaleEnabled";
uint32_t Server::sPort = NetImgui::kDefaultServerPort;
float Server::sRefreshFPSActive = 30.f;
float Server::sRefreshFPSInactive = 30.f;
float Server::sDPIScaleRatio = 1.f;
bool Server::sCompressionEnable = true;
float Server::sFontSize = 16.f;
int Server::sWindowPlacement[4] = {100, 100, 1280, 1024};
bool Server::sWindowMaximized = false;
//=================================================================================================
// The user config is created when the main config file is readonly
// Allows having a distributed config file that cannot be touched by users
static const char* GetConfigFilename(Client::eConfigType configFileType)
//=================================================================================================
{
if (configFileType == Client::eConfigType::Local) {
return "netImgui.cfg";
} else if (configFileType == Client::eConfigType::Local2nd) {
return "netImgui_2.cfg";
} else {
static char sUserSettingFile[1024];
const char* userSettingFolder =
NetImguiServer::App::HAL_GetUserSettingFolder();
NetImgui::Internal::StringFormat(
sUserSettingFile,
(userSettingFolder ? "%s\\netImgui.cfg" : "netImgui_2.cfg"),
userSettingFolder);
return sUserSettingFile;
}
}
//=================================================================================================
// Find entry index with same configId. (-1 if not found)
// Note: 'gConfigLock' should have already locked before calling this
static int FindClientIndex(uint32_t configID)
//=================================================================================================
{
if (configID != Client::kInvalidRuntimeID) {
for (int i(0); i < gConfigList.size(); ++i) {
if (gConfigList[i] && gConfigList[i]->mRuntimeID == configID) return i;
}
}
return -1;
}
//=================================================================================================
template <typename TType>
TType GetPropertyValue(const nlohmann::json& config, const char* zPropertyName,
const TType& valueDefault)
//=================================================================================================
{
const auto& valueNode = config.find(zPropertyName);
return valueNode != config.end() ? valueNode->get<TType>() : valueDefault;
}
//=================================================================================================
Client::Client()
//=================================================================================================
: mHostPort(NetImgui::kDefaultClientPort),
mRuntimeID(kInvalidRuntimeID),
mConfigType(NetImguiServer::Config::Client::eConfigType::Pending),
mDPIScaleEnabled(true),
mBlockTakeover(false),
mReadOnly(false),
mConnectAuto(false),
mConnectRequest(false),
mConnectForce(false),
mConnectStatus(eStatus::Disconnected) {
NetImgui::Internal::StringCopy(mClientName, "New Client");
NetImgui::Internal::StringCopy(mHostName, "localhost");
}
//=================================================================================================
void Client::SetConfig(const Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
// Only allow 1 transient connection to keep things clean
for (int i = 0; config.IsTransient() && i < gConfigList.size(); ++i) {
if (gConfigList[i] && gConfigList[i]->IsTransient()) {
NetImgui::Internal::netImguiDelete(gConfigList[i]);
gConfigList.erase(&gConfigList[i]);
}
}
int index = FindClientIndex(config.mRuntimeID);
// Config not found, add it to our list
if (index == -1) {
index = gConfigList.size();
gConfigList.push_back(
NetImgui::Internal::netImguiNew<NetImguiServer::Config::Client>());
}
// Update the entry
*gConfigList[index] = config;
gConfigList[index]->mRuntimeID =
(config.mRuntimeID == kInvalidRuntimeID ? gRuntimeID++
: config.mRuntimeID);
}
//=================================================================================================
void Client::DelConfig(uint32_t configID)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
NetImgui::Internal::netImguiDelete(gConfigList[index]);
gConfigList.erase(&gConfigList[index]);
}
}
//=================================================================================================
bool Client::GetConfigByID(uint32_t configID, Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
config = *gConfigList[index];
return true;
}
return false;
}
//=================================================================================================
bool Client::GetConfigByIndex(uint32_t index, Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
if (index < static_cast<uint32_t>(gConfigList.size())) {
config = *gConfigList[index];
return true;
}
return false;
}
//=================================================================================================
uint32_t Client::GetConfigCount()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
return gConfigList.size();
}
//=================================================================================================
bool Client::GetProperty_BlockTakeover(uint32_t configID)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
return gConfigList[index]->mBlockTakeover;
}
return false;
}
//=================================================================================================
void Client::SetProperty_Status(uint32_t configID, eStatus Status)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectStatus = Status;
}
}
//=================================================================================================
void Client::SetProperty_ConnectAuto(uint32_t configID, bool value)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectAuto = value;
}
}
//=================================================================================================
void Client::SetProperty_ConnectRequest(uint32_t configID, bool value,
bool force)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectRequest = value && !force;
gConfigList[index]->mConnectForce = value && force;
gConfigList[index]->mConnectLastTime = std::chrono::steady_clock::now();
}
}
//=================================================================================================
bool Client::ShouldSave(eConfigType fileConfigType) const
//=================================================================================================
{
return mConfigType == fileConfigType || mConfigType == eConfigType::Pending;
}
//=================================================================================================
void Client::SaveAll()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
std::ofstream localFile(GetConfigFilename(eConfigType::Local));
bool localIsWritable = localFile.is_open();
localFile.close();
std::ifstream local2ndFileExist(GetConfigFilename(eConfigType::Local2nd));
bool local2ndExist = local2ndFileExist.is_open();
bool local2ndIsWritable = false;
local2ndFileExist.close();
// Try saving into default config file
SaveConfigFile(eConfigType::Local, localIsWritable);
// And then in 2nd workingdir user file when 1st one is read only
if (!localIsWritable || local2ndExist) {
std::ofstream local2ndFile(GetConfigFilename(eConfigType::Local2nd));
SaveConfigFile(eConfigType::Local2nd,
!localIsWritable && local2ndIsWritable);
}
// Finally saved the shared config into user folder
SaveConfigFile(eConfigType::Shared, !localIsWritable && !local2ndIsWritable);
}
//=================================================================================================
void Client::SaveConfigFile(eConfigType configFileType,
bool writeServerSettings)
//=================================================================================================
{
nlohmann::json configRoot;
configRoot[kConfigField_Version] = eVersion::_Latest;
configRoot[kConfigField_Note] =
configFileType == eConfigType::Local
? "NetImgui Server's list of Clients (Using JSON format) Local File."
: configFileType == eConfigType::Local
? "NetImgui Server's list of Clients (Using JSON format) 2nd Local "
"File."
: "NetImgui Server's list of Clients (Using JSON format) Shared "
"File.";
if (writeServerSettings) {
configRoot[kConfigField_ServerPort] = Server::sPort;
configRoot[kConfigField_ServerRefreshActive] = Server::sRefreshFPSActive;
configRoot[kConfigField_ServerRefreshInactive] =
Server::sRefreshFPSInactive;
configRoot[kConfigField_ServerDPIScaleRatio] = Server::sDPIScaleRatio;
configRoot[kConfigField_ServerCompressionEnable] =
Server::sCompressionEnable;
configRoot[kConfigField_ServerFontSize] = Server::sFontSize;
configRoot[kConfigField_ServerWindowPlacementX] =
Server::sWindowPlacement[0];
configRoot[kConfigField_ServerWindowPlacementY] =
Server::sWindowPlacement[1];
configRoot[kConfigField_ServerWindowPlacementW] =
Server::sWindowPlacement[2];
configRoot[kConfigField_ServerWindowPlacementH] =
Server::sWindowPlacement[3];
configRoot[kConfigField_ServerWindowMaximized] = Server::sWindowMaximized;
}
int clientToSaveCount(0);
for (int i(0); i < gConfigList.size(); ++i) {
Client* pConfig = gConfigList[i];
if (pConfig && pConfig->ShouldSave(configFileType)) {
auto& config = configRoot[kConfigField_Configs][clientToSaveCount++] =
nullptr;
config[kConfigField_Name] = pConfig->mClientName;
config[kConfigField_Hostname] = pConfig->mHostName;
config[kConfigField_Hostport] = pConfig->mHostPort;
config[kConfigField_AutoConnect] = pConfig->mConnectAuto;
config[kConfigField_BlockTakeover] = pConfig->mBlockTakeover;
config[kConfigField_DPIScaleEnabled] = pConfig->mDPIScaleEnabled;
}
}
std::ofstream outputFile(GetConfigFilename(configFileType));
if (outputFile.is_open()) {
outputFile << configRoot.dump(4);
for (int i(0); clientToSaveCount > 0 && i < gConfigList.size(); ++i) {
Client* pConfig = gConfigList[i];
if (pConfig && pConfig->ShouldSave(configFileType)) {
pConfig->mReadOnly = false;
pConfig->mConfigType = configFileType;
}
}
}
}
//=================================================================================================
void Client::LoadAll()
//=================================================================================================
{
Clear();
std::lock_guard<std::mutex> guard(gConfigLock);
LoadConfigFile(eConfigType::Local);
LoadConfigFile(eConfigType::Local2nd);
LoadConfigFile(eConfigType::Shared);
}
//=================================================================================================
void Client::LoadConfigFile(eConfigType configFileType)
//=================================================================================================
{
nlohmann::json configRoot;
const char* filename = GetConfigFilename(configFileType);
std::ifstream inputFile(filename);
if (!inputFile.is_open() || inputFile.eof()) return;
configRoot = nlohmann::json::parse(inputFile, nullptr, false);
inputFile.close();
std::ofstream outputFile(filename, std::ios_base::app);
bool isWritable = outputFile.is_open() && !outputFile.eof();
outputFile.close();
uint32_t configVersion =
GetPropertyValue(configRoot, kConfigField_Version, 0u);
Server::sPort = GetPropertyValue(configRoot, kConfigField_ServerPort,
NetImgui::kDefaultServerPort);
Server::sRefreshFPSActive = GetPropertyValue(
configRoot, kConfigField_ServerRefreshActive, Server::sRefreshFPSActive);
Server::sRefreshFPSInactive =
GetPropertyValue(configRoot, kConfigField_ServerRefreshInactive,
Server::sRefreshFPSInactive);
Server::sDPIScaleRatio = GetPropertyValue(
configRoot, kConfigField_ServerDPIScaleRatio, Server::sDPIScaleRatio);
Server::sCompressionEnable =
GetPropertyValue(configRoot, kConfigField_ServerCompressionEnable,
Server::sCompressionEnable);
Server::sFontSize = GetPropertyValue(configRoot, kConfigField_ServerFontSize,
Server::sFontSize);
Server::sWindowPlacement[0] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementX,
Server::sWindowPlacement[0]);
Server::sWindowPlacement[1] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementY,
Server::sWindowPlacement[1]);
Server::sWindowPlacement[2] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementW,
Server::sWindowPlacement[2]);
Server::sWindowPlacement[3] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementH,
Server::sWindowPlacement[3]);
Server::sWindowMaximized = GetPropertyValue(
configRoot, kConfigField_ServerWindowMaximized, Server::sWindowMaximized);
if (configVersion >= static_cast<uint32_t>(eVersion::Initial)) {
for (const auto& config : configRoot[kConfigField_Configs]) {
gConfigList.push_back(
NetImgui::Internal::netImguiNew<NetImguiServer::Config::Client>());
Client* pConfig = gConfigList.back();
pConfig->mRuntimeID = gRuntimeID++;
if (config.find(kConfigField_Name) != config.end())
NetImgui::Internal::StringCopy(
pConfig->mClientName,
config[kConfigField_Name].get<std::string>().c_str());
if (config.find(kConfigField_Hostname) != config.end())
NetImgui::Internal::StringCopy(
pConfig->mHostName,
config[kConfigField_Hostname].get<std::string>().c_str());
pConfig->mHostPort =
GetPropertyValue(config, kConfigField_Hostport, pConfig->mHostPort);
pConfig->mConnectAuto = GetPropertyValue(config, kConfigField_AutoConnect,
pConfig->mConnectAuto);
pConfig->mDPIScaleEnabled = GetPropertyValue(
config, kConfigField_DPIScaleEnabled, pConfig->mDPIScaleEnabled);
pConfig->mBlockTakeover = GetPropertyValue(
config, kConfigField_BlockTakeover, pConfig->mBlockTakeover);
pConfig->mConfigType = configFileType;
pConfig->mReadOnly = !isWritable;
}
}
}
//=================================================================================================
void Client::Clear()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
while (gConfigList.size()) {
NetImgui::Internal::netImguiDelete(gConfigList.back());
gConfigList.pop_back();
}
}
} // namespace Config
} // namespace NetImguiServer
@@ -0,0 +1,160 @@
// Google modifications:
// - Added #include <chrono> for std::chrono::steady_clock::time_point
#pragma once
#include <chrono>
#include <stdint.h>
namespace NetImguiServer {
namespace Config {
//=================================================================================================
// Client Configs are used by this server to reach remote netImgui Clients.
//
// Note:For multihreading safety, we are always working with copies of the
// original data.
//
// Note:It is also possible for a remote netImgui client to connect to Server
// directly,
// in which case they don't need to have an associated config.
//=================================================================================================
class Client {
public:
using RuntimeID = uint32_t;
using TimeStamp = std::chrono::steady_clock::time_point;
static constexpr RuntimeID kInvalidRuntimeID = static_cast<RuntimeID>(0);
enum class eVersion : uint32_t {
Initial = 1, // First version save file deployed
Refresh = 2, // Added refresh rate support
DPIScale = 3, // Added DPI scaling
BlockTakeOver = 4, // Added Takeover Block
WindowPlacement = 5, // Added saving of main window location and size
_Count,
_Latest = _Count - 1
};
enum class eConfigType : uint8_t {
Pending, // New config, will try saving it in the local config
Local, // Config fetched from local config file, in the current working
// directory
Local2nd, // Config fetched from a second local config file, in the current
// working directory. Used when 'Local' file is read only
Shared, // Config fetched from the shared user folder
Transient, // Config created from connection request (command line, OS
// pipes), cannot be saved
};
enum class eStatus : uint8_t {
Disconnected, // No connection detected on client
Connecting, // This server is connecting to client
Connected, // This server is connect to client
Available, // Client already taken, but this server can take over
ErrorBusy, // Client already taken
ErrorVer, // Server/Client network api mismatch
};
// Config settings
char mClientName[128]; //!< Client display name
char mHostName[128]; //!< Client IP or remote host address to attempt
//!< connection at
uint32_t mHostPort; //!< Client Port to attempt connection at
RuntimeID mRuntimeID; //!< Unique RuntimeID used to find this Config
eConfigType mConfigType; //!< Type of the configuration
bool mDPIScaleEnabled; //!< Enable support of Font DPI scaling requests by
//!< Server
bool mBlockTakeover; //!< If another NetImguiServer is allowed to forcefully
//!< disconnect this client to connect to it
bool mReadOnly; //!< Config comes from read only file, can't be modified
bool mConnectAuto; //!< Try automatically connecting to client
// Transient values used while running
mutable bool
mConnectRequest; //!< Attempt connecting to Client, after user request
mutable bool mConnectForce; //!< Attempt connecting to Client, after user
//!< request, even if already connected
mutable eStatus mConnectStatus; //!< Connection status of associated client
mutable TimeStamp mConnectLastTime; //!< Last connection attempt time (avoid
//!< quickly retrying same client)
// Access methods
public:
Client();
inline bool IsReadOnly() const { return mReadOnly; };
inline bool IsTransient() const {
return mConfigType == eConfigType::Transient;
};
inline bool IsConnected() const {
return mConnectStatus == eStatus::Connected;
}
inline bool IsConnecting() const {
return mConnectStatus == eStatus::Connecting || mConnectRequest ||
mConnectForce;
}
inline bool IsConnectReady() const {
bool bAvailable = mConnectStatus != eStatus::Connected &&
mConnectStatus != eStatus::Connecting;
return bAvailable && (mConnectRequest || mConnectForce);
}
inline bool IsAutoConnectReady() const {
bool bAvailable = mConnectStatus != eStatus::Connected &&
mConnectStatus != eStatus::Connecting;
auto elapsedTime = std::chrono::steady_clock::now() - mConnectLastTime;
int durationSec = static_cast<int>(
std::chrono::duration_cast<std::chrono::seconds>(elapsedTime).count());
bool elapseOk =
durationSec > (mConnectStatus == eStatus::Disconnected ? 5 : 30);
return bAvailable && mConnectAuto && elapseOk;
}
// Add/Edit/Remove config
static void SetConfig(
const Client& config); //!< Add or replace a client configuration info
static void DelConfig(uint32_t configID); //!< Remove a client configuration
static bool GetConfigByID(
uint32_t configID,
Client& outConfig); //!< Find client configuration with this id (return
//!< true if found)
static bool GetConfigByIndex(
uint32_t index,
Client& outConfig); //!< Find client configuration at the x position
static uint32_t GetConfigCount();
// Set property value directly (without having to copy entire structure)
static bool GetProperty_BlockTakeover(uint32_t configID);
static void SetProperty_Status(uint32_t configID, eStatus Status);
static void SetProperty_ConnectAuto(uint32_t configID, bool value);
static void SetProperty_ConnectRequest(uint32_t configID, bool value,
bool force);
// Client Config list management
static void SaveAll();
static void LoadAll();
static void Clear();
protected:
static void SaveConfigFile(eConfigType fileConfigType,
bool writeServerSettings);
static void LoadConfigFile(eConfigType fileConfigType);
inline bool ShouldSave(eConfigType fileConfigType) const;
};
struct Server {
static uint32_t sPort; //!< Port that Server should use for connection.
//!< (Note: not really a 'Client' setting, but easier
//!< to just bundle the value here for the moment)
static float sRefreshFPSActive; //!< Refresh rate of active Window
static float sRefreshFPSInactive; //!< Refresh rate of inactive Window
static float
sDPIScaleRatio; //!< Ratio of DPI scale applied to Font size (helps with
//!< high resolution monitor, default 1.0)
static bool sCompressionEnable; //!< Ask the clients to compress their data
//!< before transmission
static float sFontSize; //!< Font size used for Server UI
static int sWindowPlacement[4]; //!< Main window position and size
//!< (x,y,width,height)
static bool sWindowMaximized;
};
} // namespace Config
} // namespace NetImguiServer
@@ -0,0 +1,577 @@
#include "NetImguiServer_Network.h"
#include <Private/NetImgui_CmdPackets.h>
#include <Private/NetImgui_Network.h>
#include <thread>
#include "NetImguiServer_App.h"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_RemoteClient.h"
#include "Private/NetImgui_WarningDisableStd.h"
using namespace NetImgui::Internal;
namespace NetImguiServer {
namespace Network {
using atomic_SocketInfo = std::atomic<::Network::SocketInfo*>;
static bool gbShutdown(false); // Set to true when NetImguiServer exiting
static atomic_SocketInfo gListenSocket(
nullptr); // Need global access to kill socket on shutdown
static std::atomic_uint32_t gActiveClientThreadCount(
0); // How many active client connection currently running
static std::atomic_bool gActiveThreadConnectOut(
false); // True while Server is still trying to connect to new clients
static std::atomic_bool gActiveThreadConnectIn(
false); // True while Server is still trying to receive connection from new
// clients
static std::atomic_uint64_t gStatsDataSent(0);
static std::atomic_uint64_t gStatsDataRcvd(0);
//=================================================================================================
// (IN) COMMAND TEXTURE
//=================================================================================================
void Communications_Incoming_CmdTexture(RemoteClient::Client& Client) {
auto pCmdTexture = reinterpret_cast<NetImgui::Internal::CmdTexture*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdTexture->mpTextureData.ToPointer();
// For debug tracking
uint32_t idx =
Client.mTextureHistoryIndex % IM_ARRAYSIZE(Client.mTextureHistory);
Client.mTextureHistory[idx].UpdateId = Client.mTextureHistoryIndex++;
Client.mTextureHistory[idx].Frame = Client.mLastDrawFrameIndex;
Client.mTextureHistory[idx].ClientId = pCmdTexture->mTextureClientID;
Client.mTextureHistory[idx].Format = pCmdTexture->mFormat;
Client.mTextureHistory[idx].isCreate =
pCmdTexture->mStatus == CmdTexture::eType::Create;
Client.mTextureHistory[idx].isDestroy =
pCmdTexture->mStatus == CmdTexture::eType::Destroy;
Client.mTextureHistory[idx].isUpdate =
pCmdTexture->mStatus == CmdTexture::eType::Update;
Client.mTextureHistory[idx].isDearImguiManaged =
pCmdTexture->mIsDearImGuiManaged != 0;
Client.mTextureHistory[idx].x = pCmdTexture->mOffsetX;
Client.mTextureHistory[idx].y = pCmdTexture->mOffsetY;
Client.mTextureHistory[idx].w = pCmdTexture->mWidth;
Client.mTextureHistory[idx].h = pCmdTexture->mHeight;
Client.ReceiveTexture(pCmdTexture);
}
//=================================================================================================
// (IN) COMMAND BACKGROUND
//=================================================================================================
void Communications_Incoming_CmdBackground(RemoteClient::Client& Client) {
auto pCmdBackground = reinterpret_cast<NetImgui::Internal::CmdBackground*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
Client.mPendingBackgroundIn.Assign(pCmdBackground);
}
//=================================================================================================
// (IN) COMMAND DRAW FRAME
//=================================================================================================
void Communications_Incoming_CmdDrawFrame(RemoteClient::Client& Client) {
auto pCmdDraw = reinterpret_cast<NetImgui::Internal::CmdDrawFrame*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdDraw->ToPointers();
Client.ReceiveDrawFrame(pCmdDraw);
}
//=================================================================================================
// (IN) COMMAND CLIPBOARD
//=================================================================================================
void Communications_Incoming_CmdClipboard(RemoteClient::Client& Client) {
auto pCmdClipboard = reinterpret_cast<NetImgui::Internal::CmdClipboard*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdClipboard->ToPointers();
Client.mPendingClipboardIn.Assign(pCmdClipboard);
}
//=================================================================================================
// Receive every commands sent by remote client and process them
//=================================================================================================
void Communications_Incoming(RemoteClient::Client& Client) {
if (::Network::DataReceivePending(Client.mpSocket)) {
//-----------------------------------------------------------------------------------------
// 1. Ready to receive new command, starts the process by reading Header
//-----------------------------------------------------------------------------------------
if (Client.mPendingRcv.IsReady()) {
Client.mCmdPendingRead = NetImgui::Internal::CmdPendingRead();
Client.mPendingRcv.pCommand = &Client.mCmdPendingRead;
Client.mPendingRcv.bAutoFree = false;
}
//-----------------------------------------------------------------------------------------
// 2. Read incoming command from server
//-----------------------------------------------------------------------------------------
if (Client.mPendingRcv.IsPending()) {
::Network::DataReceive(Client.mpSocket, Client.mPendingRcv);
// Detected a new command bigger than header, allocate memory for it
if (Client.mPendingRcv.pCommand->mSize >
sizeof(NetImgui::Internal::CmdPendingRead) &&
Client.mPendingRcv.pCommand == &Client.mCmdPendingRead) {
CmdPendingRead* pCmdHeader =
reinterpret_cast<NetImgui::Internal::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()) {
Client.mStatsDataRcvd += Client.mPendingRcv.pCommand->mSize;
Client.mLastIncomingComTime = std::chrono::steady_clock::now();
switch (Client.mPendingRcv.pCommand->mType) {
case NetImgui::Internal::CmdHeader::eCommands::Texture:
Communications_Incoming_CmdTexture(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::Background:
Communications_Incoming_CmdBackground(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::DrawFrame:
Communications_Incoming_CmdDrawFrame(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::Clipboard:
Communications_Incoming_CmdClipboard(Client);
break;
// Commands not received in main loop, by Server
case NetImgui::Internal::CmdHeader::eCommands::Version:
case NetImgui::Internal::CmdHeader::eCommands::Input:
case NetImgui::Internal::CmdHeader::eCommands::Count:
break;
}
}
// Reset pending read
if (Client.mPendingRcv.IsError()) {
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));
}
}
//=================================================================================================
// Send the updates to RemoteClient
// Ends with a Ping Command (signal a end of commands)
//=================================================================================================
void Communications_Outgoing(RemoteClient::Client& Client) {
//---------------------------------------------------------------------------------------------
// Try finishing sending a pending command to Server
//---------------------------------------------------------------------------------------------
if (Client.mPendingSend.IsPending()) {
::Network::DataSend(Client.mpSocket, Client.mPendingSend);
// Free allocated memory for command
if (Client.mPendingSend.IsDone()) {
if (Client.mPendingSend.IsError()) {
Client.mbDisconnectPending = true;
}
Client.mStatsDataSent += Client.mPendingSend.pCommand->mSize;
if (Client.mPendingSend.bAutoFree) {
netImguiDeleteSafe(Client.mPendingSend.pCommand);
}
Client.mPendingSend = PendingCom();
}
}
if (Client.mPendingSend.IsReady()) {
NetImgui::Internal::CmdClipboard* pClipboardCmd =
Client.TakePendingClipboard();
if (pClipboardCmd) {
pClipboardCmd->ToOffsets();
Client.mPendingSend.pCommand = pClipboardCmd;
Client.mPendingSend.bAutoFree = true;
}
}
if (Client.mPendingSend.IsReady()) {
NetImgui::Internal::CmdInput* pInputCmd = Client.TakePendingInput();
Client.mPendingSend.pCommand = pInputCmd;
Client.mPendingSend.bAutoFree = true;
}
}
//=================================================================================================
// Update communications stats of a client, after a frame
//=================================================================================================
void Communications_UpdateClientStats(RemoteClient::Client& Client) {
// Update data transfer stats
auto elapsedTime = std::chrono::steady_clock::now() - Client.mStatsTime;
if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTime)
.count() >= 250) {
constexpr uint64_t kHysteresis = 10; // out of 100
uint64_t newDataRcvd = Client.mStatsDataRcvd - Client.mStatsDataRcvdPrev;
uint64_t newDataSent = Client.mStatsDataSent - Client.mStatsDataSentPrev;
uint64_t tmMicrosS =
std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime)
.count();
uint32_t newDataRcvdBps =
static_cast<uint32_t>(newDataRcvd * 1000000u / tmMicrosS);
uint32_t newDataSentBps =
static_cast<uint32_t>(newDataSent * 1000000u / tmMicrosS);
Client.mStatsRcvdBps = (Client.mStatsRcvdBps * (100u - kHysteresis) +
newDataRcvdBps * kHysteresis) /
100u;
Client.mStatsSentBps = (Client.mStatsSentBps * (100u - kHysteresis) +
newDataSentBps * kHysteresis) /
100u;
gStatsDataRcvd += newDataRcvd;
gStatsDataSent += newDataSent;
Client.mStatsTime = std::chrono::steady_clock::now();
Client.mStatsDataRcvdPrev = Client.mStatsDataRcvd;
Client.mStatsDataSentPrev = Client.mStatsDataSent;
}
}
//=================================================================================================
// Keep sending/receiving commands to Remote Client, until disconnection occurs
//=================================================================================================
void Communications_ClientExchangeLoop(RemoteClient::Client* pClient) {
gActiveClientThreadCount++;
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Connected);
pClient->mbDisconnectPending = false;
pClient->mbIsConnected = true;
while (!gbShutdown && !pClient->mbDisconnectPending) {
Communications_Outgoing(*pClient);
Communications_Incoming(*pClient);
Communications_UpdateClientStats(*pClient);
}
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
NetImgui::Internal::Network::Disconnect(pClient->mpSocket);
pClient->Release();
gActiveClientThreadCount--;
}
//=================================================================================================
// Establish connection with Remote Client
// Makes sure that Server/Client are compatible
//=================================================================================================
bool Communications_InitializeClient(
NetImgui::Internal::Network::SocketInfo* pClientSocket,
RemoteClient::Client* pClient, bool ConnectForce) {
NetImgui::Internal::CmdVersion cmdVersionSend, cmdVersionRcv;
NetImgui::Internal::PendingCom PendingRcv, PendingSend;
//---------------------------------------------------------------------
// Handshake confirming connection validity
//---------------------------------------------------------------------
NetImgui::Internal::StringCopy(cmdVersionSend.mClientName, "Server");
const bool ConnectExclusive =
NetImguiServer::Config::Client::GetProperty_BlockTakeover(
pClient->mClientConfigID);
cmdVersionSend.mFlags |=
ConnectExclusive
? static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::ConnectExclusive)
: 0;
cmdVersionSend.mFlags |=
ConnectForce ? static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::ConnectForce)
: 0;
PendingSend.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionSend);
while (!gbShutdown && !PendingSend.IsDone()) {
::Network::DataSend(pClientSocket, PendingSend);
}
if (!PendingSend.IsError()) {
PendingRcv.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionRcv);
while (!PendingRcv.IsDone() &&
cmdVersionRcv.mType == CmdHeader::eCommands::Version) {
while (!gbShutdown && !::Network::DataReceivePending(pClientSocket)) {
std::this_thread::yield(); // Idle until we receive the remote data
}
::Network::DataReceive(pClientSocket, PendingRcv);
}
if (!gbShutdown && !PendingRcv.IsError()) {
//---------------------------------------------------------------------
// Connection accepted, initialize client
//---------------------------------------------------------------------
if (cmdVersionRcv.mType !=
NetImgui::Internal::CmdHeader::eCommands::Version ||
cmdVersionRcv.mVersion !=
NetImgui::Internal::CmdVersion::eVersion::_current) {
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::ErrorVer);
return false;
} else if (cmdVersionRcv.mFlags &
static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::IsConnected)) {
bool bAvailable =
(cmdVersionRcv.mFlags &
static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::IsUnavailable)) == 0;
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
bAvailable ? NetImguiServer::Config::Client::eStatus::Available
: NetImguiServer::Config::Client::eStatus::ErrorBusy);
return false;
}
pClient->Initialize();
pClient->mInfoImguiVerID = cmdVersionRcv.mImguiVerID;
pClient->mInfoNetImguiVerID = cmdVersionRcv.mNetImguiVerID;
pClient->mPendingRcv = PendingCom();
pClient->mPendingSend = PendingCom();
NetImgui::Internal::StringCopy(pClient->mInfoName,
cmdVersionRcv.mClientName);
NetImgui::Internal::StringCopy(pClient->mInfoImguiVerName,
cmdVersionRcv.mImguiVerName);
NetImgui::Internal::StringCopy(pClient->mInfoNetImguiVerName,
cmdVersionRcv.mNetImguiVerName);
NetImguiServer::Config::Client clientConfig;
if (NetImguiServer::Config::Client::GetConfigByID(
pClient->mClientConfigID, clientConfig)) {
NetImgui::Internal::StringFormat(
pClient->mWindowID, "%s (%s)##%i", pClient->mInfoName,
clientConfig.mClientName,
static_cast<int>(pClient->mClientIndex)); // Using ClientIndex as a
// window unique ID
} else {
NetImgui::Internal::StringFormat(
pClient->mWindowID, "%s##%i", pClient->mInfoName,
static_cast<int>(pClient->mClientIndex)); // Using ClientIndex as a
// window unique ID
}
return true;
}
}
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
return false;
}
//=================================================================================================
// New connection Init request.
// Start new communication thread if handshake sucessfull
//=================================================================================================
void NetworkConnectionNew(
NetImgui::Internal::Network::SocketInfo* pClientSocket,
RemoteClient::Client* pNewClient, bool ConnectForce) {
const char* zErrorMsg(nullptr);
if (pNewClient == nullptr) {
zErrorMsg = "Too many connection on server already";
} else {
NetImguiServer::Config::Client::SetProperty_Status(
pNewClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Connecting);
if (zErrorMsg == nullptr && !gbShutdown &&
Communications_InitializeClient(pClientSocket, pNewClient,
ConnectForce) == false) {
zErrorMsg = "Initialization failed. Wrong communication version?";
}
}
if (zErrorMsg == nullptr && !gbShutdown) {
pNewClient->mpSocket = pClientSocket;
std::thread(Communications_ClientExchangeLoop, pNewClient).detach();
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
if (!gbShutdown) {
if (pNewClient) {
pNewClient->mbIsFree = true;
printf("Error connecting to client '%s:%i' (%s)\n",
pNewClient->mConnectHost, pNewClient->mConnectPort, zErrorMsg);
} else {
printf("Error connecting to client (%s)\n", zErrorMsg);
}
}
}
}
//=================================================================================================
// Thread waiting on new Client Connection request
//=================================================================================================
void NetworkConnectRequest_Receive() {
uint32_t serverPort(0);
gActiveThreadConnectIn = true;
while (!gbShutdown) {
// Open (and update when needed) listening socket
if (gListenSocket == nullptr ||
serverPort != NetImguiServer::Config::Server::sPort) {
serverPort = NetImguiServer::Config::Server::sPort;
gListenSocket = NetImgui::Internal::Network::ListenStart(serverPort);
if (gListenSocket.load() == nullptr) {
printf("Failed to start connection listen on port : %i", serverPort);
std::this_thread::sleep_for(std::chrono::milliseconds(
500)); // Reduce Server listening socket open attempt frequency
}
}
// Detect connection request from Clients
if (gListenSocket.load() != nullptr) {
NetImgui::Internal::Network::SocketInfo* pClientSocket =
NetImgui::Internal::Network::ListenConnect(gListenSocket.load());
if (pClientSocket) {
uint32_t freeIndex = RemoteClient::Client::GetFreeIndex();
if (freeIndex != RemoteClient::Client::kInvalidClient) {
RemoteClient::Client& newClient =
RemoteClient::Client::Get(freeIndex);
newClient.mClientConfigID =
NetImguiServer::Config::Client::kInvalidRuntimeID;
newClient.mClientIndex = freeIndex;
NetImguiServer::App::HAL_GetSocketInfo(
pClientSocket, newClient.mConnectHost,
sizeof(newClient.mConnectHost), newClient.mConnectPort);
NetworkConnectionNew(pClientSocket, &newClient, false);
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
NetImgui::Internal::Network::SocketInfo* socketDisconnect =
gListenSocket.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(socketDisconnect);
gActiveThreadConnectIn = false;
}
//=================================================================================================
// Thread trying to reach out new Clients with a connection
//=================================================================================================
void NetworkConnectRequest_Send() {
uint64_t loopIndex(0);
NetImguiServer::Config::Client clientConfig;
gActiveThreadConnectOut = true;
while (!gbShutdown) {
uint32_t clientConfigID(NetImguiServer::Config::Client::kInvalidRuntimeID);
NetImgui::Internal::Network::SocketInfo* pClientSocket = nullptr;
// Find next client configuration to attempt connection to
bool ConnectForce = false;
uint64_t configCount =
static_cast<uint64_t>(NetImguiServer::Config::Client::GetConfigCount());
uint32_t configIdx =
configCount ? static_cast<uint32_t>(loopIndex++ % configCount) : 0;
if (NetImguiServer::Config::Client::GetConfigByIndex(configIdx,
clientConfig)) {
ConnectForce = clientConfig.mConnectForce;
if ((clientConfig.IsConnectReady() ||
clientConfig.IsAutoConnectReady()) &&
clientConfig.mHostPort != NetImguiServer::Config::Server::sPort) {
NetImguiServer::Config::Client::SetProperty_ConnectRequest(
clientConfig.mRuntimeID, false,
false); // Reset the Connection request, we are processing it
NetImguiServer::Config::Client::SetProperty_Status(
clientConfig.mRuntimeID,
NetImguiServer::Config::Client::eStatus::Disconnected);
clientConfigID =
clientConfig.mRuntimeID; // Keep track of ClientConfig we are
// attempting to connect to
pClientSocket = NetImgui::Internal::Network::Connect(
clientConfig.mHostName, clientConfig.mHostPort);
}
}
// Connection successful, find an available client slot
if (pClientSocket) {
uint32_t freeIndex = RemoteClient::Client::GetFreeIndex();
if (freeIndex != RemoteClient::Client::kInvalidClient) {
RemoteClient::Client& newClient = RemoteClient::Client::Get(freeIndex);
if (NetImguiServer::Config::Client::GetConfigByID(clientConfigID,
clientConfig)) {
NetImgui::Internal::StringCopy(newClient.mInfoName,
clientConfig.mClientName);
newClient.mConnectPort = clientConfig.mHostPort;
newClient.mClientConfigID = clientConfigID;
newClient.mClientIndex = freeIndex;
}
NetImguiServer::App::HAL_GetSocketInfo(
pClientSocket, newClient.mConnectHost,
sizeof(newClient.mConnectHost), newClient.mConnectPort);
NetworkConnectionNew(pClientSocket, &newClient, ConnectForce);
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
NetImguiServer::Config::Client::SetProperty_Status(
clientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(
500)); // There's already a wait time in Connect attempt, so no need to
// sleep for too long here
}
gActiveThreadConnectOut = false;
}
//=================================================================================================
// Initialize Networking and start listening thread
//=================================================================================================
bool Startup() {
// Relying on shared network implementation for Winsock Init
if (!NetImgui::Internal::Network::Startup()) {
return false;
}
gbShutdown = false;
gActiveClientThreadCount = 0;
std::thread(NetworkConnectRequest_Receive).detach();
std::thread(NetworkConnectRequest_Send).detach();
return true;
}
//=================================================================================================
// Send signal to terminate all communications and wait until all client have
// been released
//=================================================================================================
void Shutdown() {
gbShutdown = true;
NetImgui::Internal::Network::SocketInfo* socketDisconnect =
gListenSocket.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(socketDisconnect);
while (gActiveClientThreadCount > 0 || gActiveThreadConnectIn ||
gActiveThreadConnectOut) {
std::this_thread::yield();
}
NetImgui::Internal::Network::Shutdown();
}
//=================================================================================================
// True when we are listening for client's connection requests
//=================================================================================================
bool IsWaitingForConnection() { return gListenSocket.load() != nullptr; }
//=================================================================================================
// Total amount of data sent to clients since start
//=================================================================================================
uint64_t GetStatsDataSent() { return gStatsDataSent; }
//=================================================================================================
// Total amount of data received from clients since start
//=================================================================================================
uint64_t GetStatsDataRcvd() { return gStatsDataRcvd; }
} // namespace Network
} // namespace NetImguiServer
@@ -0,0 +1,28 @@
// Google modifications:
// - Added #include <cstdint> for uint32_t (not implicitly available in
// Google).
#pragma once
#include <cstdint>
namespace NetImguiServer {
namespace Network {
// Initialize application's networking
bool Startup();
// Shutdown application's networking
void Shutdown();
// True if this application is actively waiting for clients to reach it
// Note: False when unable to open listen socket (probably because port number
// alreay in use)
bool IsWaitingForConnection();
// Total amount of data sent to clients since start
uint64_t GetStatsDataSent();
// Total amount of data received from clients since start
uint64_t GetStatsDataRcvd();
} // namespace Network
} // namespace NetImguiServer
@@ -0,0 +1,664 @@
#include "NetImguiServer_RemoteClient.h"
#include <Private/NetImgui_CmdPackets.h>
#include <algorithm>
#include "NetImguiServer_App.h"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_UI.h"
namespace NetImguiServer {
namespace RemoteClient {
static Client* gpClients =
nullptr; // Table of all potentially connected clients to this server
static uint32_t gClientCountMax = 0;
NetImguiImDrawData::NetImguiImDrawData() : mCommandList(nullptr) {
CmdListsCount = 1; // All draws collapsed in same CmdList
CmdLists.push_back(&mCommandList);
}
Client::Client()
: mPendingTextureReadIndex(0),
mPendingTextureWriteIndex(0),
mbIsFree(true),
mbCompressionSkipOncePending(false),
mbDisconnectPending(false),
mClientConfigID(NetImguiServer::Config::Client::kInvalidRuntimeID) {}
Client::~Client() { Uninitialize(); }
void Client::ReceiveDrawFrame(NetImgui::Internal::CmdDrawFrame* pFrameData) {
if (pFrameData->mCompressed) {
if (mpFrameDrawPrev != nullptr &&
(mpFrameDrawPrev->mFrameIndex + 1) == pFrameData->mFrameIndex) {
NetImgui::Internal::CmdDrawFrame* pUncompressedFrame =
NetImgui::Internal::DecompressCmdDrawFrame(mpFrameDrawPrev,
pFrameData);
netImguiDeleteSafe(pFrameData);
pFrameData = pUncompressedFrame;
}
// Missing previous frame data
// ignore this drawframe and request a new uncompressed one to be able to
// resume display
else {
mbCompressionSkipOncePending = true;
netImguiDeleteSafe(pFrameData);
}
}
netImguiDeleteSafe(mpFrameDrawPrev);
if (pFrameData) {
// Convert DrawFrame command to Dear Imgui DrawData,
// and make it available for main thread to use in rendering
ProcessCmdDrawFrame(pFrameData);
// Update framerate
constexpr float kHysteresis = 0.025f; // Between 0 to 1.0
auto elapsedTime = std::chrono::steady_clock::now() - mLastDrawFrame;
float elapsedMs =
static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime)
.count()) /
1000.f;
mStatsDrawElapsedMs =
mStatsDrawElapsedMs * (1.f - kHysteresis) + elapsedMs * kHysteresis;
mLastDrawFrame = std::chrono::steady_clock::now();
}
}
void Client::ReceiveTexture(NetImgui::Internal::CmdTexture* pTextureCmd) {
if (pTextureCmd) {
// Wait for a free spot in the ring buffer
while (mPendingTextureWriteIndex - mPendingTextureReadIndex >=
IM_ARRAYSIZE(mpPendingTextures)) {
std::this_thread::yield();
}
uint64_t writeIndex =
mPendingTextureWriteIndex % IM_ARRAYSIZE(mpPendingTextures);
mpPendingTextures[writeIndex] = pTextureCmd;
mPendingTextureWriteIndex = mPendingTextureWriteIndex + 1;
}
}
//=================================================================================================
// Process pending texture commands received from Client
//=================================================================================================
void Client::ProcessPendingTextureCmds() {
while (mPendingTextureReadIndex != mPendingTextureWriteIndex) {
uint64_t readIndex =
mPendingTextureReadIndex % IM_ARRAYSIZE(mpPendingTextures);
NetImgui::Internal::CmdTexture* pTextureCmd = mpPendingTextures[readIndex];
auto texIt = mTextureTable.find(pTextureCmd->mTextureClientID);
NetImguiServer::App::ServerTexture* serverTex =
texIt != mTextureTable.end() ? texIt->second : nullptr;
bool isCreate =
pTextureCmd->mStatus == NetImgui::Internal::CmdTexture::eType::Create &&
pTextureCmd->mFormat != NetImgui::eTexFormat::kTexFmt_Invalid;
bool isUpdate =
pTextureCmd->mStatus == NetImgui::Internal::CmdTexture::eType::Update &&
pTextureCmd->mFormat != NetImgui::eTexFormat::kTexFmtCustom;
uint32_t texDataSize =
pTextureCmd->mSize - sizeof(NetImgui::Internal::CmdTexture);
// Delete a texture on request or when creating new one with same
// ClientTextureID
if (!isUpdate && serverTex) {
serverTex->MarkForDelete();
mTextureTable.erase(texIt);
}
// Add a texture
if (isCreate) {
serverTex = NetImguiServer::App::CreateTexture(*pTextureCmd, texDataSize);
if (serverTex) {
serverTex->mOwnerClientIndex = static_cast<int32_t>(mClientIndex);
mTextureTable.insert({pTextureCmd->mTextureClientID, serverTex});
}
}
// Update a Texture
else if (isUpdate && serverTex &&
serverTex->mTexData.Status !=
ImTextureStatus::ImTextureStatus_WantDestroy &&
serverTex->mTexData.Status !=
ImTextureStatus::ImTextureStatus_Destroyed) {
auto TexFormat = static_cast<NetImgui::eTexFormat>(pTextureCmd->mFormat);
if (serverTex->mTexData.Width <
(int)(pTextureCmd->mWidth + pTextureCmd->mOffsetX) ||
serverTex->mTexData.Height <
(int)(pTextureCmd->mHeight + pTextureCmd->mOffsetY)) {
// Update bigger than texture should not happen, but left here as a
// precaution to avoid memory corruption. Could happen if there's an
// error with client/server re-using a texture ID somehow
} else {
size_t SrcLineBytes = NetImgui::GetTexture_BytePerLine(
TexFormat, static_cast<uint32_t>(pTextureCmd->mWidth));
size_t DstLineBytes = NetImgui::GetTexture_BytePerLine(
TexFormat, static_cast<uint32_t>(serverTex->mTexData.Width));
size_t OffsetBytes =
(pTextureCmd->mOffsetY * DstLineBytes) +
NetImgui::GetTexture_BytePerLine(TexFormat, pTextureCmd->mOffsetX);
const uint8_t* pDataSrc = pTextureCmd->mpTextureData.Get();
uint8_t* pDataDst =
reinterpret_cast<uint8_t*>(serverTex->mTexData.GetPixels());
for (uint64_t y(0); y < pTextureCmd->mHeight; ++y) {
memcpy(&pDataDst[OffsetBytes + (y * DstLineBytes)],
&pDataSrc[y * SrcLineBytes], SrcLineBytes);
}
// No need to queue if status is _WantCreate
if (serverTex->mTexData.Status != ImTextureStatus_WantCreate) {
// Following code mostly copied from
// ImFontAtlasTextureBlockQueueUpload
ImTextureData* tex = &serverTex->mTexData;
ImTextureRect req = {(unsigned short)pTextureCmd->mOffsetX,
(unsigned short)pTextureCmd->mOffsetY,
(unsigned short)pTextureCmd->mWidth,
(unsigned short)pTextureCmd->mHeight};
int new_x1 = ImMax(tex->UpdateRect.w == 0
? 0
: tex->UpdateRect.x + tex->UpdateRect.w,
req.x + req.w);
int new_y1 = ImMax(tex->UpdateRect.h == 0
? 0
: tex->UpdateRect.y + tex->UpdateRect.h,
req.y + req.h);
tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x);
tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y);
tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x);
tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y);
tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x);
tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y);
tex->UsedRect.w =
(unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w,
req.x + req.w) -
tex->UsedRect.x);
tex->UsedRect.h =
(unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h,
req.y + req.h) -
tex->UsedRect.y);
tex->Status = ImTextureStatus_WantUpdates;
tex->Updates.push_back(req);
}
}
}
NetImgui::Internal::netImguiDeleteSafe(pTextureCmd);
mPendingTextureReadIndex = mPendingTextureReadIndex + 1;
}
}
void Client::Initialize() {
mConnectedTime = std::chrono::steady_clock::now();
mLastUpdateTime = std::chrono::steady_clock::now() - std::chrono::hours(1);
mLastDrawFrame = std::chrono::steady_clock::now();
mLastIncomingComTime = std::chrono::steady_clock::now();
mLastDrawFrameIndex = 0;
mStatsIndex = 0;
mStatsRcvdBps = 0;
mStatsSentBps = 0;
mStatsDrawElapsedMs = 0.f;
mStatsDataRcvd = 0;
mStatsDataSent = 0;
mStatsDataRcvdPrev = 0;
mStatsDataSentPrev = 0;
mbIsReleased = false;
mStatsTime = std::chrono::steady_clock::now();
mBGSettings =
NetImgui::Internal::CmdBackground(); // Assign background default value,
// until we receive first update
// from client
mPendingRcv = NetImgui::Internal::PendingCom();
mPendingSend = NetImgui::Internal::PendingCom();
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
NetImgui::Internal::netImguiDeleteSafe(mpFrameDrawPrev);
}
void Client::Uninitialize() {
NetImguiServer::App::HAL_DestroyRenderTarget(mpHAL_AreaRT, mHAL_AreaTexture);
for (auto serverTexIt : mTextureTable) {
if (serverTexIt.second) {
serverTexIt.second->MarkForDelete();
}
}
mTextureTable.clear();
mPendingImguiDrawDataIn.Free();
mPendingBackgroundIn.Free();
mPendingInputOut.Free();
mPendingClipboardOut.Free();
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
NetImgui::Internal::netImguiDeleteSafe(mpFrameDrawPrev);
if (mpBGContext) {
ImGui::DestroyContext(mpBGContext);
mpBGContext = nullptr;
}
mInfoName[0] = 0;
mClientIndex = 0;
mClientConfigID = NetImguiServer::Config::Client::kInvalidRuntimeID;
mbCompressionSkipOncePending = false;
mbDisconnectPending = false;
mbIsConnected = false;
mbIsFree = true;
mBGNeedUpdate = true;
}
// Used on communication thread to let main thread know this client resources
// should be deleted
void Client::Release() { mbIsReleased = true; }
bool Client::Startup(uint32_t clientCountMax) {
gClientCountMax = clientCountMax;
gpClients = new Client[clientCountMax];
return gpClients != nullptr;
}
void Client::Shutdown() {
gClientCountMax = 0;
if (gpClients) {
delete[] gpClients;
gpClients = nullptr;
}
}
uint32_t Client::GetCountMax() { return gClientCountMax; }
Client& Client::Get(uint32_t index) {
bool bValid = gpClients && index < gClientCountMax;
static Client sInvalidClient;
assert(bValid);
return bValid ? gpClients[index] : sInvalidClient;
}
uint32_t Client::GetFreeIndex() {
for (uint32_t i(0); i < gClientCountMax; ++i) {
if (gpClients[i].mbIsFree.exchange(false) == true) return i;
}
return kInvalidClient;
}
//=================================================================================================
// Get the current Dear Imgui drawdata to use for this client rendering content
//=================================================================================================
NetImguiImDrawData* Client::GetImguiDrawData(ImTextureID EmtpyTextureID) {
// DrawData's textures should now have been created, safe to use it
if (mpPendingDrawData) {
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
mpImguiDrawData = mpPendingDrawData;
mLastDrawFrameIndex = mpImguiDrawData->mFrameIndex;
mpPendingDrawData = nullptr;
}
// Check if a new frame has been added. If yes, then take ownership of it.
NetImguiImDrawData* pPendingDrawData = mPendingImguiDrawDataIn.Release();
if (pPendingDrawData) {
bool bHasPendingTextureUpdate(false);
// When a new drawdata is available, need to convert the textureid from
// NetImgui Id to the backend renderer format (texture view pointer). Done
// here (in main thread) instead of when first received on the (com thread),
// since 'mvTextures' can only be safely accessed on (main thread).
for (int i(0); i < pPendingDrawData->CmdListsCount; ++i) {
ImDrawList* pCmdList = pPendingDrawData->CmdLists[i];
for (int drawIdx(0), drawCount(pCmdList->CmdBuffer.size());
drawIdx < drawCount; ++drawIdx) {
uint64_t clientTexUserID = pCmdList->CmdBuffer[drawIdx].TexRef._TexID;
auto texIt = mTextureTable.find(clientTexUserID);
ImTextureRef serverTexRef = EmtpyTextureID;
if (texIt != mTextureTable.end() && texIt->second) {
ImTextureData* texData = &texIt->second->mTexData;
serverTexRef = texData->GetTexRef();
texIt->second->mLastFrameUsed =
pPendingDrawData->mFrameIndex; // Needed to know when it is safe
// to release the texture resource
bHasPendingTextureUpdate |=
texData->Status != ImTextureStatus::ImTextureStatus_OK;
}
pCmdList->CmdBuffer[drawIdx].TexRef = serverTexRef;
}
}
// DrawData contains textures with pending updates,
// wait 1 frame to display it
if (bHasPendingTextureUpdate) {
mpPendingDrawData = pPendingDrawData;
}
// New valid DrawData, use it for display
else {
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
mpImguiDrawData = pPendingDrawData;
mLastDrawFrameIndex = mpImguiDrawData->mFrameIndex;
}
}
return mpImguiDrawData;
}
//=================================================================================================
// Create a new Dear Imgui DrawData ready to be submitted for rendering
//=================================================================================================
void Client::ProcessCmdDrawFrame(
NetImgui::Internal::CmdDrawFrame* pCmdDrawFrame) {
constexpr float kPosRangeMin =
static_cast<float>(NetImgui::Internal::ImguiVert::kPosRange_Min);
constexpr float kPosRangeMax =
static_cast<float>(NetImgui::Internal::ImguiVert::kPosRange_Max);
constexpr float kUVRangeMin =
static_cast<float>(NetImgui::Internal::ImguiVert::kUvRange_Min);
constexpr float kUVRangeMax =
static_cast<float>(NetImgui::Internal::ImguiVert::kUvRange_Max);
if (!pCmdDrawFrame) {
return;
}
mMouseCursor = static_cast<ImGuiMouseCursor>(pCmdDrawFrame->mMouseCursor);
NetImguiImDrawData* pDrawData =
NetImgui::Internal::netImguiNew<NetImguiImDrawData>();
pDrawData->mFrameIndex = pCmdDrawFrame->mFrameIndex;
pDrawData->Valid = true;
pDrawData->TotalVtxCount =
static_cast<int>(pCmdDrawFrame->mTotalVerticeCount);
pDrawData->TotalIdxCount = static_cast<int>(pCmdDrawFrame->mTotalIndiceCount);
pDrawData->DisplayPos.x = pCmdDrawFrame->mDisplayArea[0];
pDrawData->DisplayPos.y = pCmdDrawFrame->mDisplayArea[1];
pDrawData->DisplaySize.x =
pCmdDrawFrame->mDisplayArea[2] - pCmdDrawFrame->mDisplayArea[0];
pDrawData->DisplaySize.y =
pCmdDrawFrame->mDisplayArea[3] - pCmdDrawFrame->mDisplayArea[1];
pDrawData->FramebufferScale =
ImVec2(1, 1); //! @sammyfreg Currently untested, so force set to 1
pDrawData->OwnerViewport = nullptr;
ImDrawList* pCmdList = pDrawData->CmdLists[0];
pCmdList->IdxBuffer.resize(pCmdDrawFrame->mTotalIndiceCount);
pCmdList->VtxBuffer.resize(pCmdDrawFrame->mTotalVerticeCount);
pCmdList->CmdBuffer.resize(pCmdDrawFrame->mTotalDrawCount);
pCmdList->Flags =
ImDrawListFlags_AllowVtxOffset | ImDrawListFlags_AntiAliasedLines |
ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_AntiAliasedLinesUseTex;
if (pCmdDrawFrame->mTotalDrawCount != 0) {
uint32_t indexOffset(0), vertexOffset(0);
ImDrawIdx* pIndexDst = &pCmdList->IdxBuffer[0];
ImDrawVert* pVertexDst = &pCmdList->VtxBuffer[0];
ImDrawCmd* pCommandDst = &pCmdList->CmdBuffer[0];
for (uint32_t i(0); i < pCmdDrawFrame->mDrawGroupCount; ++i) {
const NetImgui::Internal::ImguiDrawGroup& drawGroup =
pCmdDrawFrame->mpDrawGroups[i];
// Copy/Convert Indices from network command to Dear ImGui indices format
const uint16_t* pIndices =
reinterpret_cast<const uint16_t*>(drawGroup.mpIndices.Get());
if (drawGroup.mBytePerIndex == sizeof(ImDrawIdx)) {
memcpy(pIndexDst, pIndices, drawGroup.mIndiceCount * sizeof(ImDrawIdx));
} else {
for (uint32_t indexIdx(0); indexIdx < drawGroup.mIndiceCount;
++indexIdx) {
pIndexDst[indexIdx] = static_cast<ImDrawIdx>(pIndices[indexIdx]);
}
}
// Convert the Vertices from network command to Dear Imgui Format
const NetImgui::Internal::ImguiVert* pVertexSrc =
drawGroup.mpVertices.Get();
for (uint32_t vtxIdx(0); vtxIdx < drawGroup.mVerticeCount; ++vtxIdx) {
pVertexDst[vtxIdx].pos.x =
(static_cast<float>(pVertexSrc[vtxIdx].mPos[0]) *
(kPosRangeMax - kPosRangeMin)) /
static_cast<float>(0xFFFF) +
kPosRangeMin + drawGroup.mReferenceCoord[0];
pVertexDst[vtxIdx].pos.y =
(static_cast<float>(pVertexSrc[vtxIdx].mPos[1]) *
(kPosRangeMax - kPosRangeMin)) /
static_cast<float>(0xFFFF) +
kPosRangeMin + drawGroup.mReferenceCoord[1];
pVertexDst[vtxIdx].uv.x =
(static_cast<float>(pVertexSrc[vtxIdx].mUV[0]) *
(kUVRangeMax - kUVRangeMin)) /
static_cast<float>(0xFFFF) +
kUVRangeMin;
pVertexDst[vtxIdx].uv.y =
(static_cast<float>(pVertexSrc[vtxIdx].mUV[1]) *
(kUVRangeMax - kUVRangeMin)) /
static_cast<float>(0xFFFF) +
kUVRangeMin;
pVertexDst[vtxIdx].col = pVertexSrc[vtxIdx].mColor;
}
// Convert the Draws from network command to Dear Imgui Format
const NetImgui::Internal::ImguiDraw* pDrawSrc = drawGroup.mpDraws.Get();
for (uint32_t drawIdx(0); drawIdx < drawGroup.mDrawCount; ++drawIdx) {
pCommandDst[drawIdx].ClipRect.x = pDrawSrc[drawIdx].mClipRect[0];
pCommandDst[drawIdx].ClipRect.y = pDrawSrc[drawIdx].mClipRect[1];
pCommandDst[drawIdx].ClipRect.z = pDrawSrc[drawIdx].mClipRect[2];
pCommandDst[drawIdx].ClipRect.w = pDrawSrc[drawIdx].mClipRect[3];
pCommandDst[drawIdx].VtxOffset =
pDrawSrc[drawIdx].mVtxOffset + vertexOffset;
pCommandDst[drawIdx].IdxOffset =
pDrawSrc[drawIdx].mIdxOffset + indexOffset;
pCommandDst[drawIdx].ElemCount = pDrawSrc[drawIdx].mIdxCount;
pCommandDst[drawIdx].UserCallback = nullptr;
pCommandDst[drawIdx].UserCallbackData = nullptr;
pCommandDst[drawIdx].TexRef._TexID =
NetImgui::Internal::ConvertFromClientTexID(
pDrawSrc[drawIdx].mClientTexId);
}
pIndexDst += drawGroup.mIndiceCount;
pVertexDst += drawGroup.mVerticeCount;
pCommandDst += drawGroup.mDrawCount;
indexOffset += drawGroup.mIndiceCount;
vertexOffset += drawGroup.mVerticeCount;
}
}
mpFrameDrawPrev = pCmdDrawFrame;
mPendingImguiDrawDataIn.Assign(pDrawData);
}
//=================================================================================================
// Note: Caller must take ownership of item and delete the object
//=================================================================================================
NetImgui::Internal::CmdInput* Client::TakePendingInput() {
return mPendingInputOut.Release();
}
NetImgui::Internal::CmdClipboard* Client::TakePendingClipboard() {
return mPendingClipboardOut.Release();
}
//=================================================================================================
// Capture current received Dear ImGui input, and forward it to the active
// client Note: Even if a client is not focused, we are still sending it the
// mouse position,
// so it can update its UI.
// Note: Sending an input command, will trigger a redraw on the client,
// which we receive on the server afterward
//=================================================================================================
void Client::CaptureImguiInput() {
// Capture input from Dear ImGui (when this Client is in focus)
const ImGuiIO& io = ImGui::GetIO();
if (mbIsVisible) {
if (ImGui::IsWindowFocused()) {
const size_t initialSize = mPendingInputChars.size();
const size_t addedChar = io.InputQueueCharacters.size();
if (addedChar) {
mPendingInputChars.resize(initialSize + addedChar);
memcpy(&mPendingInputChars[initialSize], io.InputQueueCharacters.Data,
addedChar * sizeof(ImWchar));
}
mMouseWheelPos[0] += io.MouseWheel;
mMouseWheelPos[1] += io.MouseWheelH;
}
// Update persistent mouse status
if (ImGui::IsMousePosValid(&io.MousePos)) {
mMousePos[0] = io.MousePos.x - ImGui::GetCursorScreenPos().x;
mMousePos[1] = io.MousePos.y - ImGui::GetCursorScreenPos().y;
}
}
// This method is tied to the Server VSync setting, which might not match our
// client desired refresh setting When client refresh drops too much, take
// into consideration the lenght of the Server frame, to evaluate if we should
// update or not
bool wasActive = mbIsActive;
mbIsActive = ImGui::IsWindowFocused();
float clientFPS = !mbIsVisible ? 0.f
: !mbIsActive
? NetImguiServer::Config::Server::sRefreshFPSInactive
: NetImguiServer::Config::Server::sRefreshFPSActive;
float elapsedMs =
static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - mLastUpdateTime)
.count()) /
1000.f;
bool bRefresh = (wasActive != mbIsActive) || elapsedMs > 1000.f / 60.f;
if (!bRefresh) {
return;
}
// Try to re-acquire unsent input command, or create a new one if none pending
NetImgui::Internal::CmdInput* pNewInput = TakePendingInput();
pNewInput =
pNewInput
? pNewInput
: NetImgui::Internal::netImguiNew<NetImgui::Internal::CmdInput>();
// Create new Input command to send to client
NetImguiServer::Config::Client config;
NetImguiServer::Config::Client::GetConfigByID(mClientConfigID, config);
pNewInput->mScreenSize[0] =
static_cast<uint16_t>(ImGui::GetContentRegionAvail().x);
pNewInput->mScreenSize[1] =
static_cast<uint16_t>(ImGui::GetContentRegionAvail().y);
pNewInput->mMousePos[0] = static_cast<int16_t>(mMousePos[0]);
pNewInput->mMousePos[1] = static_cast<int16_t>(mMousePos[1]);
pNewInput->mMouseWheelVert = mMouseWheelPos[0];
pNewInput->mMouseWheelHoriz = mMouseWheelPos[1];
pNewInput->mCompressionUse =
NetImguiServer::Config::Server::sCompressionEnable;
pNewInput->mCompressionSkip = mbCompressionSkipOncePending;
pNewInput->mFontDPIScaling = 1.f;
pNewInput->mDesiredFps = clientFPS;
if (config.mDPIScaleEnabled) {
float scale = ImGui::GetMainViewport()->DpiScale;
scale = scale > 1.f ? scale : 1.f;
pNewInput->mFontDPIScaling =
1.f + (scale - 1.f) * NetImguiServer::Config::Server::sDPIScaleRatio;
}
mbCompressionSkipOncePending = false;
if ((mbIsVisible && mbIsActive) && ImGui::IsWindowFocused()) {
// Mouse Buttons Inputs
// If Dear ImGui Update this enum, must also adjust our enum copy
static_assert(
static_cast<int>(NetImgui::Internal::CmdInput::NetImguiMouseButton::
ImGuiMouseButton_COUNT) ==
static_cast<int>(ImGuiMouseButton_::ImGuiMouseButton_COUNT),
"Update the NetImgui enum to match the updated Dear ImGui enum");
pNewInput->mMouseDownMask = 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Left)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Left
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Right)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Right
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Middle)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Middle
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(3)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Extra1
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(4)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Extra2
: 0;
// Keyboard / Gamepads Inputs
// If Dear ImGui Update their enum, must also adjust our enum copy,
// so adding a few check to detect a change
#define EnumKeynameTest(KEYNAME) \
static_cast<int>(NetImgui::Internal::CmdInput::NetImguiKeys::KEYNAME) == \
static_cast<int>(ImGuiKey::KEYNAME - ImGuiKey::ImGuiKey_NamedKey_BEGIN), \
"Update the NetImgui enum to match the updated Dear ImGui enum"
static_assert(
NetImgui::Internal::CmdInput::NetImguiKeys::ImGuiKey_COUNT ==
(ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN),
"Update the NetImgui enum to match the updated Dear ImGui enum");
static_assert(EnumKeynameTest(ImGuiKey_Tab));
static_assert(EnumKeynameTest(ImGuiKey_Escape));
static_assert(EnumKeynameTest(ImGuiKey_RightSuper));
static_assert(EnumKeynameTest(ImGuiKey_Apostrophe));
static_assert(EnumKeynameTest(ImGuiKey_Keypad0));
static_assert(EnumKeynameTest(ImGuiKey_CapsLock));
static_assert(EnumKeynameTest(ImGuiKey_GamepadStart));
static_assert(EnumKeynameTest(ImGuiKey_GamepadLStickUp));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModCtrl));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModShift));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModAlt));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModSuper));
static_assert(EnumKeynameTest(ImGuiKey_GamepadStart));
static_assert(EnumKeynameTest(ImGuiKey_GamepadR3));
static_assert(EnumKeynameTest(ImGuiKey_GamepadLStickUp));
static_assert(EnumKeynameTest(ImGuiKey_GamepadRStickRight));
// Save every keydown status to out bitmask
uint64_t valueMask(0);
for (uint32_t i(0); i < ImGuiKey::ImGuiKey_NamedKey_COUNT; ++i) {
valueMask |=
ImGui::IsKeyDown(static_cast<ImGuiKey>(ImGuiKey_NamedKey_BEGIN + i))
? 0x0000000000000001ull << (i % 64)
: 0;
if (((i % 64) == 63) || i == (ImGuiKey::ImGuiKey_NamedKey_COUNT - 1)) {
pNewInput->mInputDownMask[i / 64] = valueMask;
valueMask = 0;
}
}
// Save analog keys (gamepad)
for (uint32_t i(0); i < NetImgui::Internal::CmdInput::kAnalog_Count; ++i) {
pNewInput->mInputAnalog[i] =
ImGui::GetIO()
.KeysData[NetImgui::Internal::CmdInput::kAnalog_First + i]
.AnalogValue;
}
}
// Copy waiting characters inputs
size_t addedKeyCount =
std::min<size_t>(NetImgui::Internal::ArrayCount(pNewInput->mKeyChars) -
pNewInput->mKeyCharCount,
mPendingInputChars.size());
if (addedKeyCount) {
memcpy(&pNewInput->mKeyChars[pNewInput->mKeyCharCount],
&mPendingInputChars[0], addedKeyCount * sizeof(ImWchar));
pNewInput->mKeyCharCount += static_cast<uint16_t>(addedKeyCount);
size_t charRemainCount = mPendingInputChars.size() - addedKeyCount;
if (charRemainCount > 0) {
memcpy(&mPendingInputChars[0], &mPendingInputChars[addedKeyCount],
mPendingInputChars.size() - addedKeyCount);
}
mPendingInputChars.resize(charRemainCount);
}
mPendingInputOut.Assign(pNewInput);
mLastUpdateTime = std::chrono::steady_clock::now();
}
} // namespace RemoteClient
} // namespace NetImguiServer
@@ -0,0 +1,199 @@
#pragma once
#include <Private/NetImgui_CmdPackets.h>
#include <chrono>
#include <unordered_map>
#include <vector>
#include "NetImguiServer_App.h"
namespace NetImguiServer {
namespace RemoteClient {
//=================================================================================================
// ImDrawData wrapper
//
// Allocate a single ImDrawList and assign it to itself.
//
// This child class leave the original ImDrawData behavior intact, but add the
// proper memory freeing of the ImDrawList member.
//=================================================================================================
struct NetImguiImDrawData : ImDrawData {
NetImguiImDrawData();
ImDrawList mCommandList;
uint64_t mFrameIndex = 0;
};
//=================================================================================================
// All info needed by the server to communicate with a remote client, and render
// its content
//=================================================================================================
struct Client {
static constexpr uint32_t kInvalidClient = static_cast<uint32_t>(-1);
using ExchPtrInput =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdInput>;
using ExchPtrClipboard =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdClipboard>;
using ExchPtrBackground =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdBackground>;
using ExchPtrImguiDraw = NetImgui::Internal::ExchangePtr<NetImguiImDrawData>;
using TextureTable = std::unordered_map<uint64_t, App::ServerTexture*>;
struct TexUpdateInfo {
uint64_t ClientId;
uint64_t Frame;
uint32_t UpdateId;
uint32_t Format;
uint32_t x;
uint32_t y;
uint32_t w;
uint32_t h;
uint32_t isCreate : 1;
uint32_t isDestroy : 1;
uint32_t isUpdate : 1;
uint32_t isDearImguiManaged : 1;
};
Client();
~Client();
Client(const Client&) = delete;
Client(const Client&&) = delete;
void operator=(const Client&) = delete;
void Initialize();
void Uninitialize();
void Release();
bool IsValid() const;
void ReceiveTexture(NetImgui::Internal::CmdTexture*);
void ReceiveDrawFrame(NetImgui::Internal::CmdDrawFrame*);
void ProcessCmdDrawFrame(NetImgui::Internal::CmdDrawFrame* pCmdDrawFrame);
NetImguiImDrawData* GetImguiDrawData(
ImTextureID EmtpyTextureID); // Get current active Imgui draw data
void CaptureImguiInput();
NetImgui::Internal::CmdInput* TakePendingInput();
NetImgui::Internal::CmdClipboard* TakePendingClipboard();
void ProcessPendingTextureCmds();
static bool Startup(uint32_t clientCountMax);
static void Shutdown();
static uint32_t GetCountMax();
static uint32_t GetFreeIndex();
static Client& Get(uint32_t index);
void* mpHAL_AreaRT = nullptr;
ImTextureData mHAL_AreaTexture;
uint16_t mAreaRTSizeX = 0; //!< Currently allocated RenderTarget size
uint16_t mAreaRTSizeY = 0; //!< Currently allocated RenderTarget size
uint16_t mAreaSizeX = 0; //!< Available area size available to remote client
uint16_t mAreaSizeY = 0; //!< Available area size available to remote client
char mInfoName[128] = {};
char mWindowID[128 + 16] = {};
char mInfoImguiVerName[16] = {};
char mInfoNetImguiVerName[16] = {};
uint32_t mInfoImguiVerID = 0;
uint32_t mInfoNetImguiVerID = 0;
char mConnectHost[64] = {}; //!< Connected Hostname of this remote client
int mConnectPort = 0; //!< Connected Port of this remote client
NetImguiImDrawData* mpImguiDrawData =
nullptr; //!< Current Imgui Data that this client is the owner of
NetImguiImDrawData* mpPendingDrawData =
nullptr; //!< Pending Imgui Data that has to have 1 frame display delay,
//!< to avoid issue with textures with pending updates
NetImgui::Internal::CmdDrawFrame* mpFrameDrawPrev =
nullptr; //!< Last valid DrawDrame (used by com thread, to uncompress
//!< data)
TextureTable mTextureTable; //!< Table matching client TextureUserID to
//!< textures allocated on Server for it
ExchPtrImguiDraw
mPendingImguiDrawDataIn; //!< Pending received Imgui DrawData, waiting to
//!< be taken ownership of
ExchPtrBackground mPendingBackgroundIn; //!< Background settings received and
//!< waiting to update client setting
ExchPtrClipboard mPendingClipboardIn; //!< Clipboard received from Client and
//!< waiting to be processed on Server
ExchPtrInput
mPendingInputOut; //!< Input command waiting to be sent out to client
ExchPtrClipboard mPendingClipboardOut; //!< Clipboard command waiting to be
//!< sent out to client
std::vector<ImWchar>
mPendingInputChars; //!< Captured Imgui characters input waiting to be
//!< added to new InputCmd
NetImgui::Internal::CmdTexture* mpPendingTextures[1024] =
{}; //!< Textures commands waiting to be processed in main update loop
std::atomic_uint64_t mPendingTextureReadIndex;
std::atomic_uint64_t mPendingTextureWriteIndex;
bool mbIsVisible = false; //!< If currently shown
bool mbIsActive = false; //!< Is the current active window (will receive
//!< input, only one is true at a time)
bool mbIsReleased = false; //!< If released in com thread and main thread
//!< should delete resources
bool mbIsConnected =
false; //!< If connected to a remote client. Set to false in Unitialize,
//!< after mIsRelease is set to unload resources
std::atomic_bool
mbIsFree; //!< If available to use for a new connected client
std::atomic_bool
mbCompressionSkipOncePending; //!< When we detect invalid previous
//!< DrawFrame command, cancel compression
//!< for 1 frame, to get good data
std::atomic_bool mbDisconnectPending; //!< Terminate Client/Server coms
std::chrono::steady_clock::time_point
mConnectedTime; //!< When the connection was established with this remote
//!< client
std::chrono::steady_clock::time_point
mLastUpdateTime; //!< When the client last send a content refresh request
std::chrono::steady_clock::time_point
mLastDrawFrame; //!< When we last receive a new drawframe commant
std::chrono::steady_clock::time_point
mLastIncomingComTime; //!< When we last received a valid command from
//!< client (to detect timeout)
uint32_t mClientConfigID =
0; //!< ID of ClientConfig that connected (if connection came from our
//!< list of ClientConfigs)
uint32_t mClientIndex = 0; //!< Entry idx into table of connected clients
uint64_t mStatsDataRcvd =
0; //!< Current amount of Bytes received since connected
uint64_t mStatsDataSent =
0; //!< Current amount of Bytes sent to client since connected
uint64_t mStatsDataRcvdPrev =
0; //!< Last amount of Bytes received since connected
uint64_t mStatsDataSentPrev =
0; //!< Last amount of Bytes sent to client since connected
std::chrono::steady_clock::time_point
mStatsTime; //!< Time when info was collected (with history of last x
//!< values)
uint32_t mStatsRcvdBps = 0; //!< Average Bytes received per second
uint32_t mStatsSentBps = 0; //!< Average Bytes sent per second
float mStatsDrawElapsedMs =
0.f; //!< Average milliseconds between 2 draw requests
uint32_t mStatsIndex = 0;
float mMousePos[2] = {0, 0};
float mMouseWheelPos[2] = {0, 0};
ImGuiMouseCursor mMouseCursor =
ImGuiMouseCursor_None; // Last mosue cursor remote client requested
ImGuiContext* mpBGContext =
nullptr; // Special Imgui Context used to render the background (only
// updated when needed)
bool mBGNeedUpdate = true; // Let engine know that we should regenerate the
// background draw commands
NetImgui::Internal::Network::SocketInfo* mpSocket =
nullptr; //!< Socket used for communications
NetImgui::Internal::CmdBackground
mBGSettings; //!< Settings for client background drawing settings
NetImgui::Internal::CmdPendingRead
mCmdPendingRead; //!< Used to get info on the next incoming command from
//!< Client
NetImgui::Internal::PendingCom
mPendingRcv; //!< Data being currently received from Client
NetImgui::Internal::PendingCom
mPendingSend; //!< Data being currently sent to Client
TexUpdateInfo mTextureHistory[256] =
{}; //!< Keeps track of texture changes (for debug info)
uint32_t mTextureHistoryIndex = 0;
uint64_t mLastDrawFrameIndex =
0; //!< Last frame index of valid drawdata drawn
};
} // namespace RemoteClient
} // namespace NetImguiServer
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
// Google modifications:
// - Added #include <cstdint> for uint32_t (not implicitly available in Google)
#pragma once
#include <cstdint>
#include <imgui.h>
namespace NetImguiServer {
namespace App {
struct ServerTexture;
}
} // namespace NetImguiServer
namespace NetImguiServer {
namespace UI {
constexpr uint32_t kWindowDPIDefault = 96;
bool Startup();
void Shutdown();
ImVec4 DrawImguiContent();
void DrawCenteredBackground(const App::ServerTexture* Texture,
const ImVec4& tint = ImVec4(1.f, 1.f, 1.f, 1.f));
float GetDisplayFPS();
const App::ServerTexture* GetBackgroundTexture();
} // namespace UI
} // namespace NetImguiServer