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:
committed by
Copybara-Service
parent
c7b6e0b8dd
commit
ca5337161d
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
+664
@@ -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
|
||||
+199
@@ -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
|
||||
Reference in New Issue
Block a user