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

PiperOrigin-RevId: 954769526
Change-Id: I4cdee083082f7d55c2babd5e664ec0283a2aaf63
This commit is contained in:
Matija Kecman
2026-07-27 12:31:15 -07:00
committed by Copybara-Service
parent c7b6e0b8dd
commit ca5337161d
40 changed files with 15483 additions and 0 deletions
@@ -0,0 +1,3 @@
# Disable clang-format for third-party code to keep diffs minimal during Copybara updates.
DisableFormat: true
SortIncludes: Never
@@ -0,0 +1,75 @@
# Copyright 2026 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# NetImgui client library (vendored, MIT licensed — see LICENSE).
#
# This CMakeLists is included from two different build contexts:
# 1. The main MuJoCo tree under Emscripten (for the web viewer WASM client),
# where the `dear_imgui` target exists.
# 2. The Python bindings build (for the headless_ui pybind module), where Dear
# ImGui is an imported archive named `mujoco::dep::dear_imgui`.
if(TARGET netimgui)
return()
endif()
# Resolve the Dear ImGui target for the current build context. Check the
# imported archive first: in the Python bindings build a pybind module named
# `dear_imgui` is also defined (the Python ImGui bindings), which must not be
# confused with the C++ library.
if(TARGET mujoco::dep::dear_imgui)
set(NETIMGUI_IMGUI_TARGET mujoco::dep::dear_imgui)
elseif(TARGET dear_imgui)
set(NETIMGUI_IMGUI_TARGET dear_imgui)
else()
message(WARNING "No Dear ImGui target found. netimgui will not be built.")
return()
endif()
add_library(netimgui STATIC
Code/Client/Private/NetImgui_Api.cpp
Code/Client/Private/NetImgui_Client.cpp
Code/Client/Private/NetImgui_CmdPackets_DrawFrame.cpp
)
if(EMSCRIPTEN)
# WebSocket-based networking for the browser (WASM) build.
target_sources(netimgui PRIVATE google/NetImgui_NetworkWASM.cpp)
# emscripten_websocket_* requires the JS websocket library.
target_link_options(netimgui PUBLIC -lwebsocket.js)
elseif(WIN32)
# Winsock2 networking for Windows.
target_sources(netimgui PRIVATE Code/Client/Private/NetImgui_NetworkWin32.cpp)
# The source self-links ws2_32 via `#pragma comment(lib, ...)` under MSVC;
# link it explicitly so non-MSVC toolchains (e.g. clang-cl) resolve it too.
target_link_libraries(netimgui PUBLIC ws2_32)
elseif(UNIX)
# BSD-socket networking for Linux and macOS (both are UNIX).
target_sources(netimgui PRIVATE Code/Client/Private/NetImgui_NetworkPosix.cpp)
else()
message(WARNING "NetImgui networking is not available on this platform. "
"netimgui will not be built.")
return()
endif()
target_include_directories(netimgui
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR} # google/logging.h
${CMAKE_CURRENT_SOURCE_DIR}/Code/Client # NetImgui_Api.h
${CMAKE_CURRENT_SOURCE_DIR}/Code/Client/Private # NetImgui_CmdPackets.h etc.
)
target_link_libraries(netimgui PUBLIC ${NETIMGUI_IMGUI_TARGET})
set_target_properties(netimgui PROPERTIES POSITION_INDEPENDENT_CODE ON)
@@ -0,0 +1,353 @@
#pragma once
//=================================================================================================
//! @Name : NetImgui
//=================================================================================================
//! @author : Sammy Fatnassi
//! @date : 2026/01/04
//! @version : v1.13.0
//! @Details : For integration info :
//! https://github.com/sammyfreg/netImgui/wiki
//=================================================================================================
#define NETIMGUI_VERSION "1.13.0" // Release of v 1.13
#define NETIMGUI_VERSION_NUM 11300
//-------------------------------------------------------------------------------------------------
// Deactivate a few warnings to allow Imgui header include
// without generating warnings in maximum level '-Wall'
//-------------------------------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic push
// ImGui.h warnings(s)
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
#pragma clang diagnostic ignored \
"-Wreserved-identifier" // Enum values using '__' or member starting with
// '_' in imgui.h
// NetImgui_Api.h Warning(s)
#pragma clang diagnostic ignored \
"-Wzero-as-null-pointer-constant" // Not using nullptr in case this file is
// used in pre C++11
#elif defined(_MSC_VER)
#pragma warning(push)
// ImGui.h warnings(s)
#pragma warning( \
disable : 4514) // 'xxx': unreferenced inline function has been removed
#pragma warning(disable : 4710) // 'xxx': function not inlined
#pragma warning( \
disable \
: 4820) // 'xxx': 'yyy' bytes padding added after data member 'zzz'
#pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for
// memory load if /Qspectre switch specified
#endif
//=================================================================================================
// Include the user config file. It should contain the include for :
// 'imgui.h' : always
// 'imgui_internal.h' when 'NETIMGUI_INTERNAL_INCLUDE' is defined
//=================================================================================================
#ifdef NETIMGUI_IMPLEMENTATION
#define NETIMGUI_INTERNAL_INCLUDE
#include "NetImgui_Config.h"
#undef NETIMGUI_INTERNAL_INCLUDE
#else
#include "NetImgui_Config.h"
#endif
//-------------------------------------------------------------------------------------------------
// If 'NETIMGUI_ENABLED' hasn't been defined yet (in project settings or
// NetImgui_Config.h') we define this library as 'Disabled'
//-------------------------------------------------------------------------------------------------
#ifndef NETIMGUI_ENABLED
#define NETIMGUI_ENABLED 0
#endif
//-------------------------------------------------------------------------------------------------
// NetImgui needs to detect Dear ImGui to be active, otherwise we disable it
// When including this header, make sure imgui.h is included first
// (either always included in NetImgui_config.h or have it included after
// Imgui.h in your cpp)
//-------------------------------------------------------------------------------------------------
#if !defined(IMGUI_VERSION)
#undef NETIMGUI_ENABLED
#define NETIMGUI_ENABLED 0
#endif
//-------------------------------------------------------------------------------------------------
// Control support for native Dear ImGui texture backend support
// At the moment, meant to always be active on recent Dear Imgui version (1.92+)
//-------------------------------------------------------------------------------------------------
#ifndef NETIMGUI_IMGUI_TEXTURES_ENABLED
#ifdef IMGUI_HAS_TEXTURES
#define NETIMGUI_IMGUI_TEXTURES_ENABLED 1
#else
#define NETIMGUI_IMGUI_TEXTURES_ENABLED 0
#endif
#endif
#if NETIMGUI_ENABLED
#include <stdint.h>
//=================================================================================================
// Default Build settings defines values
// Assign default values when not set in user NetImgui_Config.h
//=================================================================================================
//-------------------------------------------------------------------------------------------------
// Prepended to functions signature, for dll export/import
//-------------------------------------------------------------------------------------------------
#ifndef NETIMGUI_API
#define NETIMGUI_API \
IMGUI_API // Use same value as defined by Dear ImGui by default
#endif
//-------------------------------------------------------------------------------------------------
// Enable TCP socket 'reuse port' option when opening it as a 'listener'.
// Note: Can help when unable to open a socket because it wasn't properly
// released after a crash.
//-------------------------------------------------------------------------------------------------
#ifndef NETIMGUI_FORCE_TCP_LISTEN_BINDING
#define NETIMGUI_FORCE_TCP_LISTEN_BINDING \
0 // Doesn't seem to be needed on Window/Linux
#endif
//-------------------------------------------------------------------------------------------------
// Enable Dear ImGui Callbacks support for BeginFrame/Render automatic
// interception. Note: Avoid having to replace ImGui::BeginFrame/ImGui::Render
// with in library user code, by
// 'NetImgui::NewFrame/NetImgui::EndFrame'. But prevent
//benefit of skipping frame draw when unneeded, that 'NetImgui::NewFrame' can
//provide. For more info, consult 'SampleNewFrame.cpp'. Needs Dear ImGui 1.81+
//-------------------------------------------------------------------------------------------------
#ifndef NETIMGUI_IMGUI_CALLBACK_ENABLED
#define NETIMGUI_IMGUI_CALLBACK_ENABLED \
(IMGUI_VERSION_NUM >= 18100) // Not supported pre Dear ImGui 1.81
#endif
namespace NetImgui {
//=================================================================================================
// List of texture format supported
//=================================================================================================
enum eTexFormat {
// Match Dear Imgui 1.92 'ImTextureFormat'
kTexFmtRGBA8,
kTexFmtA8,
// Support of 'user defined' texture format.
// Implementation must be added on both client and Server code.
// Search for TEXTURE_CUSTOM_SAMPLE for example implementation.
kTexFmtCustom,
//
kTexFmt_Count,
kTexFmt_Invalid = kTexFmt_Count
};
//=================================================================================================
// Data Compression wanted status
//=================================================================================================
enum eCompressionMode {
kForceDisable, // Disable data compression for communications
kForceEnable, // Enable data compression for communications
kUseServerSetting // Use Server setting for compression (default)
};
//-------------------------------------------------------------------------------------------------
// Function typedefs
//-------------------------------------------------------------------------------------------------
typedef void (*ThreadFunctPtr)(void threadedFunction(void* pClientInfo),
void* pClientInfo);
typedef void (*FontCreateFuncPtr)(float PreviousDPIScale, float NewDPIScale);
//=================================================================================================
// Initialize the Network Library
//=================================================================================================
NETIMGUI_API bool Startup(void);
//=================================================================================================
// Free Resources
// Wait until all communication threads have terminated before returning
//=================================================================================================
NETIMGUI_API void Shutdown();
//=================================================================================================
// Establish a connection between the NetImgui server application and this
// client.
//
// Can connect with NetImgui Server application by either reaching it directly
// using 'ConnectToApp' or waiting for Server to reach us after Client called
// 'ConnectFromApp'.
//
// Note: Start a new communication thread using std::Thread by default,
// but can receive custom
// thread start function instead (Look at ClientExample
//'CustomCommunicationThread').
//-------------------------------------------------------------------------------------------------
// clientName : Client name displayed in the Server's clients
// list serverHost : NetImgui Server Application address
// (Ex1: 127.0.0.2, Ex2: localhost) serverPort : PortID of the
// NetImgui Server application to connect to clientPort : PortID
// this Client should wait for connection from Server application threadFunction
// : User provided function to launch new networking thread.
// Use
//'DefaultStartCommunicationThread' by default (uses 'std::thread').
// fontCreateFunction : User provided function to call when the Server expect
// an update of
// the font atlas, because of a
//monitor DPI change. When left to nullptr, uses 'ImGuiIO.FontGlobalScale'
//instead to increase text size, with blurier results. NOTE: Not used by Dear
//ImGui 1.92+, unneeded with font update support.
//=================================================================================================
NETIMGUI_API bool ConnectToApp(const char* clientName, const char* serverHost,
uint32_t serverPort = kDefaultServerPort,
ThreadFunctPtr threadFunction = 0,
FontCreateFuncPtr FontCreateFunction = 0);
NETIMGUI_API bool ConnectFromApp(const char* clientName,
uint32_t clientPort = kDefaultClientPort,
ThreadFunctPtr threadFunction = 0,
FontCreateFuncPtr fontCreateFunction = 0);
//=================================================================================================
// Request a disconnect from the NetImguiServer application
//=================================================================================================
NETIMGUI_API void Disconnect(void);
//=================================================================================================
// True if connected to the NetImguiServer application
//=================================================================================================
NETIMGUI_API bool IsConnected(void);
//=================================================================================================
// True if connection request is waiting to be completed. For example, while
// waiting for Server to reach ud after having called 'ConnectFromApp()'
//=================================================================================================
NETIMGUI_API bool IsConnectionPending(void);
//=================================================================================================
// True when Dear ImGui is currently expecting draw commands
// This means that we are between NewFrame() and EndFrame()
//=================================================================================================
NETIMGUI_API bool IsDrawing(void);
//=================================================================================================
// True when we are currently drawing on the NetImguiServer application
// Means that we are between NewFrame() and EndFrame() of drawing for remote
// application
//=================================================================================================
NETIMGUI_API bool IsDrawingRemote(void);
//=================================================================================================
// Send an updated texture used by imgui, to the NetImguiServer application
// Note: To remove a texture, set pData to nullptr
// Note: User needs to provide a valid 'dataSize' when using format
// 'kTexFmtCustom',
// can be ignored otherwise
// Note: Can now rely on native Dear ImGui managed texture support to let the
// system handle their
// creation/update/destruction automatically, without needing to
//call this function (since Dear ImGui 1.92+. See 'SampleTextures').
//=================================================================================================
NETIMGUI_API void SendDataTexture(ImTextureID textureId, void* pData,
uint16_t width, uint16_t height,
eTexFormat format, uint32_t dataSize = 0);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
NETIMGUI_API void SendDataTexture(const ImTextureRef& textureRef, void* pData,
uint16_t width, uint16_t height,
eTexFormat format, uint32_t dataSize = 0);
#endif
//=================================================================================================
// Start a new Imgui Frame and wait for Draws commands, using ImContext that was
// active on connect. Returns true if we are awaiting a new ImGui frame.
//
// All ImGui drawing should be skipped when return is false.
//
// Note: This code can be used instead, to know if you should be drawing or not
// :
// 'if( !NetImgui::IsDrawing() )'
//
// Note: If your code cannot handle skipping a ImGui frame, leave
// 'bSupportFrameSkip=false',
// and this function will always call 'ImGui::NewFrame()'
//internally and return true
//
// Note: With Dear ImGui 1.81+, you can keep using the
// ImGui::BeginFrame()/Imgui::Render()
// without having to use NetImgui::NewFrame()/NetImgui::EndFrame()
// (unless wanting to support frame skip)
//=================================================================================================
NETIMGUI_API bool NewFrame(bool bSupportFrameSkip = false);
//=================================================================================================
// Process all receives draws, send them to remote connection and restore the
// ImGui Context
//=================================================================================================
NETIMGUI_API void EndFrame(void);
//=================================================================================================
// Return the context associated to this remote connection. Null when not
// connected.
//=================================================================================================
NETIMGUI_API ImGuiContext* GetContext();
//=================================================================================================
// Set the remote client background color and texture
// Note: If no TextureID is specified, will use the default server texture
//=================================================================================================
NETIMGUI_API void SetBackground(const ImVec4& bgColor);
NETIMGUI_API void SetBackground(const ImVec4& bgColor,
const ImVec4& textureTint);
NETIMGUI_API void SetBackground(const ImVec4& bgColor,
const ImVec4& textureTint,
ImTextureID bgTextureID);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
NETIMGUI_API void SetBackground(const ImVec4& bgColor,
const ImVec4& textureTint,
const ImTextureRef& bgTextureRef);
#endif
//=================================================================================================
// Control the data compression for communications between Client/Server
//=================================================================================================
NETIMGUI_API void SetCompressionMode(eCompressionMode eMode);
NETIMGUI_API eCompressionMode GetCompressionMode();
//=================================================================================================
// Helper functions
//=================================================================================================
NETIMGUI_API uint8_t GetTexture_BitsPerPixel(eTexFormat eFormat);
NETIMGUI_API uint32_t GetTexture_BytePerLine(eTexFormat eFormat,
uint32_t pixelWidth);
NETIMGUI_API uint32_t GetTexture_BytePerImage(eTexFormat eFormat,
uint32_t pixelWidth,
uint32_t pixelHeight);
} // namespace NetImgui
//=================================================================================================
// Optional single include compiling option
// Note: User wanting to avoid adding the few NetImgui sources files to their
// project,
// can instead define 'NETIMGUI_IMPLEMENTATION' *once* before
//including 'NetImgui_Api.h' to pull all the needed cpp files alongside for
//compilation
//=================================================================================================
#if defined(NETIMGUI_IMPLEMENTATION)
#include "Private/NetImgui_Api.cpp"
#include "Private/NetImgui_Client.cpp"
#include "Private/NetImgui_CmdPackets_DrawFrame.cpp"
#include "Private/NetImgui_NetworkPosix.cpp"
#include "Private/NetImgui_NetworkUE4.cpp"
#include "Private/NetImgui_NetworkWin32.cpp"
#endif
#endif // NETIMGUI_ENABLED
//-------------------------------------------------------------------------------------------------
// Re-Enable the Deactivated warnings
//-------------------------------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif
@@ -0,0 +1,71 @@
#pragma once
//=================================================================================================
// Enable code compilation for this library
// Note: Useful to disable 'netImgui' on unsupported builds while keeping
// functions declared
//=================================================================================================
#ifndef NETIMGUI_ENABLED
#define NETIMGUI_ENABLED 1
#endif
#if NETIMGUI_ENABLED
#include <imgui.h>
#ifdef NETIMGUI_INTERNAL_INCLUDE
#include <imgui_internal.h> // Only needed when compiling NetImgui, not when using the NetImgui Api
#include "Private/NetImgui_WarningDisableImgui.h" // Disable some extra warning generated by imgui_internal in '-Wall'
#include "Private/NetImgui_WarningReenable.h"
#endif
#endif // NETIMGUI_ENABLED
//=================================================================================================
// Default Ports used to reach the Server or the Client (listen port for
// incoming connection)
//=================================================================================================
namespace NetImgui {
enum Constants {
kDefaultServerPort = 8888, //!< Default port Server waits for a connection
kDefaultClientPort = 8889 //!< Default port Client waits for a connection
};
}
//=================================================================================================
// Enable default Win32/Posix networking code
// Note: By default, netImgui uses Winsock on Windows and Posix sockets
// on other platforms
//
// The use your own code, turn off both
//NETIMGUI_WINSOCKET_ENABLED, NETIMGUI_POSIX_SOCKETS_ENABLED and provide your
//own implementation of the functions declared in 'NetImgui_Network.h'.
//
// As an example, 'SampleCompression' disable default com
//implementation and use its own
//=================================================================================================
#if !defined(NETIMGUI_WINSOCKET_ENABLED) && !defined(__UNREAL__)
#ifdef _WIN32
#define NETIMGUI_WINSOCKET_ENABLED \
1 // Project needs 'ws2_32.lib' added to input libraries
#else
#define NETIMGUI_WINSOCKET_ENABLED 0
#endif
#endif
#if !defined(NETIMGUI_POSIX_SOCKETS_ENABLED) && !defined(__UNREAL__)
#define NETIMGUI_POSIX_SOCKETS_ENABLED !(NETIMGUI_WINSOCKET_ENABLED)
#endif
//=================================================================================================
// Various build settings define
// Note: for more information, please look in 'NetImgui_Api.h' for description
// and default values
//=================================================================================================
// #define NETIMGUI_IMGUI_CALLBACK_ENABLED (IMGUI_VERSION_NUM >=
// 18100) // Not supported pre Dear ImGui 1.81 #define
// NETIMGUI_FORCE_TCP_LISTEN_BINDING 0
// // Doesn't seem to be needed on Window/Linux #define NETIMGUI_API
// IMGUI_API // Use same value as
// defined by Dear ImGui by default
@@ -0,0 +1,948 @@
// Google modifications:
// - Adding missing headers.
// - Added logging for network events.
#include "NetImgui_Shared.h"
#include "NetImgui_WarningDisable.h"
#if NETIMGUI_ENABLED
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <algorithm>
#include <thread>
#include "NetImgui_Client.h"
#include "NetImgui_CmdPackets.h"
#include "NetImgui_Network.h"
#include "google/logging.h"
using namespace NetImgui::Internal;
namespace NetImgui {
static Client::ClientInfo* gpClientInfo = nullptr;
bool ProcessInputData(Client::ClientInfo& client);
//=================================================================================================
void DefaultStartCommunicationThread(void ComFunctPtr(void*), void* pClient)
//=================================================================================================
{
// Visual Studio 2017 generate this warning on std::thread, avoid the warning
// preventing build
#if defined(_MSC_VER) && (_MSC_VER < 1920)
#pragma warning(push)
#pragma warning(disable \
: 4625) // 'std::_LaunchPad<_Target>' : copy constructor was
// implicitly defined as deleted
#pragma warning(disable : 4626) // 'std::_LaunchPad<_Target>' : assignment
// operator was implicitly defined as deleted
#endif
std::thread(ComFunctPtr, pClient).detach();
#if defined(_MSC_VER) && (_MSC_VER < 1920)
#pragma warning(pop)
#endif
}
//=================================================================================================
bool ConnectToApp(const char* clientName, const char* ServerHost,
uint32_t serverPort, ThreadFunctPtr threadFunction,
FontCreateFuncPtr FontCreateFunction)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
Disconnect();
while (client.IsActive()) std::this_thread::yield();
client.ContextRestore(); // Restore context setting override, after a
// disconnect
client.ContextRemoveHooks(); // Remove hooks callback only when completely
// disconnected
StringCopy(
client.mName,
(clientName == nullptr || clientName[0] == 0 ? "Unnamed" : clientName));
client.mpSocketPending = Network::Connect(ServerHost, serverPort);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
IM_UNUSED(FontCreateFunction);
#else
client.mFontCreationFunction = FontCreateFunction;
#endif
if (client.mpSocketPending.load() != nullptr) {
client.ContextInitialize();
threadFunction = threadFunction == nullptr ? DefaultStartCommunicationThread
: threadFunction;
threadFunction(Client::CommunicationsConnect, &client);
}
return client.IsActive();
}
//=================================================================================================
bool ConnectFromApp(const char* clientName, uint32_t serverPort,
ThreadFunctPtr threadFunction,
FontCreateFuncPtr FontCreateFunction)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
Disconnect();
while (client.IsActive()) std::this_thread::yield();
client.ContextRestore(); // Restore context setting override, after a
// disconnect
client.ContextRemoveHooks(); // Remove hooks callback only when completly
// disconnected
StringCopy(
client.mName,
(clientName == nullptr || clientName[0] == 0 ? "Unnamed" : clientName));
client.mpSocketPending = Network::ListenStart(serverPort);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
IM_UNUSED(FontCreateFunction);
#else
client.mFontCreationFunction = FontCreateFunction;
#endif
client.mThreadFunction = (threadFunction == nullptr)
? DefaultStartCommunicationThread
: threadFunction;
if (client.mpSocketPending.load() != nullptr) {
client.ContextInitialize();
client.mSocketListenPort = serverPort;
client.mThreadFunction(Client::CommunicationsHost, &client);
}
return client.IsActive();
}
//=================================================================================================
void Disconnect(void)
//=================================================================================================
{
if (!gpClientInfo) return;
// Attempt fake connection on local socket waiting for a Server connection,
// so the blocking operation can terminate and release the communication
// thread
Client::ClientInfo& client = *gpClientInfo;
client.mbDisconnectPending = true;
client.mbDisconnectListen = true;
if (client.mpSocketListen.load() != nullptr &&
client.mSocketListenPort != 0) {
Network::SocketInfo* pFakeSocket =
Network::Connect("127.0.0.1", client.mSocketListenPort);
client.mSocketListenPort = 0;
client.mbDisconnectPending = true;
if (pFakeSocket) {
Network::Disconnect(pFakeSocket);
}
}
// Wait for connection attempt to complete and fail
while (client.mbComInitActive || client.mbClientThreadActive);
// If fake connection to exit Listening failed, force disconnect socket
// directly even though it might potentially cause a race condition
Network::SocketInfo* pListenSocket = client.mpSocketListen.exchange(nullptr);
if (pListenSocket) {
Network::Disconnect(pListenSocket);
}
Network::SocketInfo* pPendingSocket =
client.mpSocketPending.exchange(nullptr);
if (pPendingSocket) {
Network::Disconnect(pPendingSocket);
}
}
//=================================================================================================
bool IsConnected(void)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
// If disconnected in middle of a remote frame drawing,
// want to behave like it is still connected to finish frame properly
return client.IsConnected() || IsDrawingRemote();
}
//=================================================================================================
bool IsConnectionPending(void)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
return client.IsConnectPending();
}
//=================================================================================================
bool IsDrawing(void)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
return client.mbIsDrawing;
}
//=================================================================================================
bool IsDrawingRemote(void)
//=================================================================================================
{
if (!gpClientInfo) return false;
Client::ClientInfo& client = *gpClientInfo;
return IsDrawing() && client.mbIsRemoteDrawing;
}
//=================================================================================================
bool NewFrame(bool bSupportFrameSkip)
//=================================================================================================
{
if (!gpClientInfo || gpClientInfo->mbIsDrawing) return false;
Client::ClientInfo& client = *gpClientInfo;
ScopedBool scopedInside(client.mbInsideNewEnd, true);
// ImGui Newframe handled by remote connection settings
if (NetImgui::IsConnected()) {
ImGui::SetCurrentContext(client.mpContext);
// Save current context settings and override settings to fit our netImgui
// usage
if (!client.IsContextOverriden()) {
client.ContextOverride();
}
auto elapsedCheck =
std::chrono::steady_clock::now() - client.mLastOutgoingDrawCheckTime;
auto elapsedDraw =
std::chrono::steady_clock::now() - client.mLastOutgoingDrawTime;
auto elapsedCheckMs =
static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(elapsedCheck)
.count()) /
1000.f;
auto elapsedDrawMs =
static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(elapsedDraw)
.count()) /
1000.f;
client.mLastOutgoingDrawCheckTime = std::chrono::steady_clock::now();
// Update input and see if remote netImgui expect a new frame
client.mbValidDrawFrame = false;
// Take into account delay until next method call, for more precise fps
bool shouldDraw =
client.mDesiredFps > 0.f &&
(elapsedDrawMs + elapsedCheckMs / 2.f) > (1000.f / client.mDesiredFps);
VLOG(2,
"NewFrame: elapsedDrawMs=%.2f, elapsedCheckMs=%.2f, shouldDraw=%s, "
"mDesiredFps=%.1f (target=%.1f)",
elapsedDrawMs, elapsedCheckMs, shouldDraw ? "Y" : "N",
client.mDesiredFps, 1000.f / client.mDesiredFps);
if (shouldDraw) {
VLOG(2, "NewFrame: shouldDraw=Y, triggering draw!");
client.mLastOutgoingDrawTime = std::chrono::steady_clock::now();
client.mbValidDrawFrame = true;
client.mSavedDisplaySize = ImGui::GetIO().DisplaySize;
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
client.mFontSavedScaling = ImGui::GetStyle().FontScaleDpi;
ImGui::GetStyle().FontScaleDpi = client.mFontServerScale;
#else
// We are about to start drawing for remote context, check for font data
// update
const ImFontAtlas* pFonts = ImGui::GetIO().Fonts;
if (pFonts->TexPixelsAlpha8 &&
(pFonts->TexPixelsAlpha8 != client.mpFontTextureData ||
client.mFontTextureID != ConvertToClientTexID(pFonts->TexID))) {
uint8_t* pPixelData(nullptr);
int width(0), height(0);
ImGui::GetIO().Fonts->GetTexDataAsAlpha8(&pPixelData, &width, &height);
SendDataTexture(pFonts->TexID, pPixelData, static_cast<uint16_t>(width),
static_cast<uint16_t>(height), eTexFormat::kTexFmtA8);
}
// No font texture has been sent to the netImgui server, you can either
// 1. Leave font data available in ImGui (not call ImGui::ClearTexData)
// for netImgui to auto send it
// 2. Manually call 'NetImgui::SendDataTexture' with font texture data
assert(client.mbFontUploaded);
#endif
}
ProcessInputData(client);
// Update current active content with our time
ImGui::GetIO().DeltaTime =
std::max<float>(1.f / 1000.f, elapsedCheckMs / 1000.f);
// NetImgui isn't waiting for a new frame, try to skip drawing when caller
// supports it
if (!client.mbValidDrawFrame && bSupportFrameSkip) {
return false;
}
}
// Regular Imgui NewFrame
else {
// Restore context setting override, after a disconnect
client.ContextRestore();
// Remove hooks callback only when completly disconnected
if (!client.IsConnectPending()) {
client.ContextRemoveHooks();
}
}
// A new frame is expected, update the current time of the drawing context,
// and let Imgui know to prepare a new drawing frame
client.mbIsRemoteDrawing = NetImgui::IsConnected();
client.mbIsDrawing = true;
// Reset Dear ImGui managed Textures status if not handled by backend, to not
// re-process the same elements (active on 1.92+)
client.TextureTrackingClear();
// This function can be called from a 'NewFrame' ImGui hook, we should not
// start a new frame again
if (!client.mbInsideHook) {
ImGui::NewFrame();
}
return true;
}
//=================================================================================================
void EndFrame(void)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
ScopedBool scopedInside(client.mbInsideNewEnd, true);
if (client.mbIsDrawing) {
// Must be fetched before 'Render'
ImGuiMouseCursor Cursor = ImGui::GetMouseCursor();
// This function can be called from a 'EndFrame' ImGui hook, in which case
// no need to call this again
if (!client.mbInsideHook) {
ImGui::Render();
}
// Detect all DearImgui ImGui managed Textures waiting for updates before
// backend process them (active on 1.92+)
client.TextureTrackingUpdate();
// Prepare the Dear Imgui DrawData for later transmission to Server
ImDrawData* imDrawData = ImGui::GetDrawData();
client.ProcessDrawData(imDrawData, Cursor);
// Detect change to background settings by user, and forward them to server
if (client.mBGSetting != client.mBGSettingSent) {
CmdBackground* pCmdBackground = netImguiNew<CmdBackground>();
*pCmdBackground = client.mBGSetting;
client.mBGSettingSent = client.mBGSetting;
client.mPendingBackgroundOut.Assign(pCmdBackground);
}
if (client.mbIsRemoteDrawing) {
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
// Clear the ImGui Draw Data while leaving the texture updates intact
// This allows the backend to process the texture updates/status normally
// (usually done in Render backend implementation) while not displaying
// anything.
if (imDrawData) {
imDrawData->CmdLists.resize(0);
imDrawData->CmdListsCount = 0;
imDrawData->TotalIdxCount = 0;
imDrawData->TotalVtxCount = 0;
}
ImGui::GetStyle().FontScaleDpi = client.mFontSavedScaling;
#endif
// Restore display size, so we never lose original setting
// that may get updated after initial connection
ImGui::GetIO().DisplaySize = client.mSavedDisplaySize;
}
}
VLOG(2, "EndFrame: mbIsDrawing was %s, mbIsRemoteDrawing was %s",
client.mbIsDrawing ? "Y" : "N", client.mbIsRemoteDrawing ? "Y" : "N");
client.mbIsRemoteDrawing = false;
client.mbIsDrawing = false;
client.mbValidDrawFrame = false;
}
//=================================================================================================
ImGuiContext* GetContext()
//=================================================================================================
{
if (!gpClientInfo) return nullptr;
Client::ClientInfo& client = *gpClientInfo;
return client.mpContext;
}
//=================================================================================================
void SendDataTexture(ImTextureID textureId, void* pData, uint16_t width,
uint16_t height, eTexFormat format, uint32_t dataSize)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
ClientTextureID clientTexID = ConvertToClientTexID(textureId);
// Add/Update a texture
if (pData != nullptr) {
CmdTexture* pCmdTexture =
client.TextureCmdAllocate(clientTexID, width, height, format, dataSize);
if (pCmdTexture) {
memcpy(pCmdTexture->mpTextureData.Get(), pData, dataSize);
pCmdTexture->mpTextureData.ToOffset();
client.TextureTrackingAdd(*pCmdTexture);
// Detects when user is sending the font texture
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
ScopedImguiContext scopedCtx(
client.mpContext ? client.mpContext : ImGui::GetCurrentContext());
if (ImGui::GetIO().Fonts && ImGui::GetIO().Fonts->TexID == textureId) {
client.mbFontUploaded |= true;
client.mpFontTextureData = ImGui::GetIO().Fonts->TexPixelsAlpha8;
client.mFontTextureID = clientTexID;
}
#endif
}
}
// Texture to remove
else {
client.TextureTrackingRem(clientTexID);
}
}
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
//=================================================================================================
void SendDataTexture(const ImTextureRef& textureRef, void* pData,
uint16_t width, uint16_t height, eTexFormat format,
uint32_t dataSize)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
ClientTextureID clientTexID = ConvertToClientTexID(textureRef);
// Add/Update a texture
if (pData != nullptr) {
CmdTexture* pCmdTexture =
client.TextureCmdAllocate(clientTexID, width, height, format, dataSize);
if (pCmdTexture) {
memcpy(pCmdTexture->mpTextureData.Get(), pData, dataSize);
pCmdTexture->mpTextureData.ToOffset();
client.TextureTrackingAdd(*pCmdTexture);
}
}
// Texture to remove
else {
client.TextureTrackingRem(clientTexID);
}
}
#endif // NETIMGUI_IMGUI_TEXTURES_ENABLED
//=================================================================================================
void SetBackground(const ImVec4& bgColor)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
client.mBGSetting = NetImgui::Internal::CmdBackground();
client.mBGSetting.mClearColor[0] = bgColor.x;
client.mBGSetting.mClearColor[1] = bgColor.y;
client.mBGSetting.mClearColor[2] = bgColor.z;
client.mBGSetting.mClearColor[3] = bgColor.w;
}
//=================================================================================================
void SetBackground(const ImVec4& bgColor, const ImVec4& textureTint)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
client.mBGSetting.mClearColor[0] = bgColor.x;
client.mBGSetting.mClearColor[1] = bgColor.y;
client.mBGSetting.mClearColor[2] = bgColor.z;
client.mBGSetting.mClearColor[3] = bgColor.w;
client.mBGSetting.mTextureTint[0] = textureTint.x;
client.mBGSetting.mTextureTint[1] = textureTint.y;
client.mBGSetting.mTextureTint[2] = textureTint.z;
client.mBGSetting.mTextureTint[3] = textureTint.w;
client.mBGSetting.mTextureId =
NetImgui::Internal::CmdBackground::kDefaultTexture;
}
//=================================================================================================
void SetBackground(const ImVec4& bgColor, const ImVec4& textureTint,
ImTextureID bgTextureID)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
client.mBGSetting.mClearColor[0] = bgColor.x;
client.mBGSetting.mClearColor[1] = bgColor.y;
client.mBGSetting.mClearColor[2] = bgColor.z;
client.mBGSetting.mClearColor[3] = bgColor.w;
client.mBGSetting.mTextureTint[0] = textureTint.x;
client.mBGSetting.mTextureTint[1] = textureTint.y;
client.mBGSetting.mTextureTint[2] = textureTint.z;
client.mBGSetting.mTextureTint[3] = textureTint.w;
uint64_t texId64(0);
reinterpret_cast<ImTextureID*>(&texId64)[0] = bgTextureID;
client.mBGSetting.mTextureId = texId64;
}
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
//=================================================================================================
void SetBackground(const ImVec4& bgColor, const ImVec4& textureTint,
const ImTextureRef& bgTextureRef)
//=================================================================================================
{
SetBackground(bgColor, textureTint, ConvertToClientTexID(bgTextureRef));
}
#endif
//=================================================================================================
void SetCompressionMode(eCompressionMode eMode)
//=================================================================================================
{
if (!gpClientInfo) return;
Client::ClientInfo& client = *gpClientInfo;
client.mClientCompressionMode = static_cast<uint8_t>(eMode);
}
//=================================================================================================
eCompressionMode GetCompressionMode()
//=================================================================================================
{
if (!gpClientInfo) return eCompressionMode::kUseServerSetting;
Client::ClientInfo& client = *gpClientInfo;
return static_cast<eCompressionMode>(client.mClientCompressionMode);
}
//=================================================================================================
bool Startup(void)
//=================================================================================================
{
if (!gpClientInfo) {
gpClientInfo = netImguiNew<Client::ClientInfo>();
}
return Network::Startup();
}
//=================================================================================================
void Shutdown()
//=================================================================================================
{
if (!gpClientInfo) return;
Disconnect();
while (gpClientInfo->IsActive()) std::this_thread::yield();
Network::Shutdown();
netImguiDeleteSafe(gpClientInfo);
}
//=================================================================================================
ImGuiContext* CloneContext(ImGuiContext* pSourceContext)
//=================================================================================================
{
// Create a context duplicate
ScopedImguiContext scopedSourceCtx(pSourceContext);
ImGuiContext* pContextClone = ImGui::CreateContext(ImGui::GetIO().Fonts);
ImGuiIO& sourceIO = ImGui::GetIO();
ImGuiStyle& sourceStyle = ImGui::GetStyle();
{
ScopedImguiContext scopedCloneCtx(pContextClone);
ImGuiIO& newIO = ImGui::GetIO();
ImGuiStyle& newStyle = ImGui::GetStyle();
// Import the style/options settings of current context, into this one
memcpy(&newStyle, &sourceStyle, sizeof(newStyle));
memcpy(&newIO, &sourceIO, sizeof(newIO));
// memcpy(newIO.KeyMap, sourceIO.KeyMap, sizeof(newIO.KeyMap));
newIO.InputQueueCharacters.Data = nullptr;
newIO.InputQueueCharacters.Size = 0;
newIO.InputQueueCharacters.Capacity = 0;
}
return pContextClone;
}
//=================================================================================================
uint8_t GetTexture_BitsPerPixel(eTexFormat eFormat)
//=================================================================================================
{
switch (eFormat) {
case eTexFormat::kTexFmtA8:
return 8 * 1;
case eTexFormat::kTexFmtRGBA8:
return 8 * 4;
case eTexFormat::kTexFmtCustom:
return 0;
case eTexFormat::kTexFmt_Invalid:
return 0;
}
return 0;
}
//=================================================================================================
uint32_t GetTexture_BytePerLine(eTexFormat eFormat, uint32_t pixelWidth)
//=================================================================================================
{
uint32_t bitsPerPixel =
static_cast<uint32_t>(GetTexture_BitsPerPixel(eFormat));
return pixelWidth * bitsPerPixel / 8;
// Note: If adding support to BC compression format, have to take into account
// 4x4 size alignment
}
//=================================================================================================
uint32_t GetTexture_BytePerImage(eTexFormat eFormat, uint32_t pixelWidth,
uint32_t pixelHeight)
//=================================================================================================
{
return GetTexture_BytePerLine(eFormat, pixelWidth) * pixelHeight;
// Note: If adding support to BC compression format, have to take into account
// 4x4 size alignement
}
static inline void AddKeyEvent(const Client::ClientInfo& client,
const CmdInput* pCmdInput,
CmdInput::NetImguiKeys netimguiKey,
ImGuiKey imguiKey) {
uint32_t valIndex = netimguiKey / 64;
uint64_t valMask = 0x0000000000000001ull << (netimguiKey % 64);
#if IMGUI_VERSION_NUM < 18700
IM_UNUSED(client);
ImGui::GetIO().KeysDown[imguiKey] =
(pCmdInput->mInputDownMask[valIndex] & valMask) != 0;
#else
bool bChanged = (pCmdInput->mInputDownMask[valIndex] ^
client.mPreviousInputState.mInputDownMask[valIndex]) &
valMask;
if (bChanged) {
ImGui::GetIO().AddKeyEvent(imguiKey,
pCmdInput->mInputDownMask[valIndex] & valMask);
}
#endif
}
static inline void AddKeyAnalogEvent(const Client::ClientInfo& client,
const CmdInput* pCmdInput,
CmdInput::NetImguiKeys netimguiKey,
ImGuiKey imguiKey) {
uint32_t valIndex = netimguiKey / 64;
uint64_t valMask = 0x0000000000000001ull << (netimguiKey % 64);
assert(CmdInput::kAnalog_First <= static_cast<uint32_t>(netimguiKey) &&
static_cast<uint32_t>(netimguiKey) <= CmdInput::kAnalog_Last);
#if IMGUI_VERSION_NUM < 18700
IM_UNUSED(client);
IM_UNUSED(pCmdInput);
IM_UNUSED(netimguiKey);
IM_UNUSED(imguiKey);
#else
int indexAnalog = netimguiKey - CmdInput::kAnalog_First;
indexAnalog = indexAnalog >= static_cast<int>(CmdInput::kAnalog_Count)
? CmdInput::kAnalog_Count - 1
: indexAnalog;
float analogValue = pCmdInput->mInputAnalog[indexAnalog];
bool bChanged = (pCmdInput->mInputDownMask[valIndex] ^
client.mPreviousInputState.mInputDownMask[valIndex]) &
valMask;
bChanged |= abs(client.mPreviousInputState.mInputAnalog[indexAnalog] -
analogValue) > 0.001f;
if (bChanged) {
ImGui::GetIO().AddKeyAnalogEvent(
imguiKey, pCmdInput->mInputDownMask[valIndex] & valMask, analogValue);
}
#endif
}
//=================================================================================================
bool ProcessInputData(Client::ClientInfo& client)
//=================================================================================================
{
// Update the current clipboard data received from Server
CmdClipboard* pCmdClipboardNew = client.mPendingClipboardIn.Release();
if (pCmdClipboardNew) {
netImguiDeleteSafe(client.mpCmdClipboard);
client.mpCmdClipboard = pCmdClipboardNew;
}
// Update the keyboard/mouse/gamepad inputs
CmdInput* pCmdInputNew = client.mPendingInputIn.Release();
bool hasNewInput = pCmdInputNew != nullptr;
CmdInput* pCmdInput = hasNewInput ? pCmdInputNew : client.mpCmdInputPending;
ImGuiIO& io = ImGui::GetIO();
if (pCmdInput) {
const float wheelY = pCmdInput->mMouseWheelVert -
client.mPreviousInputState.mMouseWheelVertPrev;
const float wheelX = pCmdInput->mMouseWheelHoriz -
client.mPreviousInputState.mMouseWheelHorizPrev;
io.DisplaySize =
ImVec2(pCmdInput->mScreenSize[0], pCmdInput->mScreenSize[1]);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
client.mFontServerScale = pCmdInput->mFontDPIScaling;
#else
// User assigned a function callback handling FontScaling,
// use it to request a Font update on DPI scaling change on the server
if (gpClientInfo->mFontCreationFunction != nullptr) {
io.FontGlobalScale = 1.f;
if (abs(gpClientInfo->mFontServerScale - pCmdInput->mFontDPIScaling) >
0.01f) {
gpClientInfo->mFontCreationFunction(gpClientInfo->mFontSavedScaling,
pCmdInput->mFontDPIScaling);
client.mFontServerScale = pCmdInput->mFontDPIScaling;
}
}
// Client doesn't support regenerating the font at new DPI
// Use FontGlobalScale to affect rendering size, resulting in blurrier
// result
else {
io.FontGlobalScale = pCmdInput->mFontDPIScaling;
}
#endif
#if IMGUI_VERSION_NUM < 18700
io.MousePos = ImVec2(pCmdInput->mMousePos[0], pCmdInput->mMousePos[1]);
io.MouseWheel = wheelY;
io.MouseWheelH = wheelX;
for (uint32_t i(0);
i < CmdInput::NetImguiMouseButton::ImGuiMouseButton_COUNT; ++i) {
io.MouseDown[i] =
(pCmdInput->mMouseDownMask & (0x0000000000000001ull << i)) != 0;
}
#define AddInputDown(KEYNAME) \
AddKeyEvent(client, pCmdInput, CmdInput::KEYNAME, ImGuiKey_::KEYNAME);
AddInputDown(ImGuiKey_Tab) AddInputDown(ImGuiKey_LeftArrow) AddInputDown(
ImGuiKey_RightArrow) AddInputDown(ImGuiKey_UpArrow)
AddInputDown(ImGuiKey_DownArrow) AddInputDown(ImGuiKey_PageUp)
AddInputDown(ImGuiKey_PageDown) AddInputDown(ImGuiKey_Home)
AddInputDown(ImGuiKey_End) AddInputDown(ImGuiKey_Insert)
AddInputDown(ImGuiKey_Delete) AddInputDown(
ImGuiKey_Backspace) AddInputDown(ImGuiKey_Space)
AddInputDown(ImGuiKey_Enter)
AddInputDown(ImGuiKey_Escape) AddInputDown(
ImGuiKey_A) // for text edit CTRL+A: select all
AddInputDown(ImGuiKey_C) // for text edit CTRL+C: copy
AddInputDown(ImGuiKey_V) // for text edit CTRL+V: paste
AddInputDown(ImGuiKey_X) // for text edit CTRL+X: cut
AddInputDown(ImGuiKey_Y) // for text edit CTRL+Y: redo
AddInputDown(ImGuiKey_Z) // for text edit CTRL+Z: undo
io.KeyShift = pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModShift);
io.KeyCtrl = pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModCtrl);
io.KeyAlt = pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModAlt);
io.KeySuper = pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModSuper);
#else
#if IMGUI_VERSION_NUM < 18837
#define ImGuiKey ImGuiKey_
#endif
// At the moment All Dear Imgui version share the same ImGuiKey_ enum (with a
// 512 value offset), but could change in the future, so convert from our own
// enum version, to Dear ImGui.
#define AddInputDown(KEYNAME) \
AddKeyEvent(client, pCmdInput, CmdInput::KEYNAME, ImGuiKey::KEYNAME);
#define AddAnalogInputDown(KEYNAME) \
AddKeyAnalogEvent(client, pCmdInput, CmdInput::KEYNAME, ImGuiKey::KEYNAME);
AddInputDown(ImGuiKey_Tab) AddInputDown(ImGuiKey_LeftArrow) AddInputDown(
ImGuiKey_RightArrow) AddInputDown(ImGuiKey_UpArrow) AddInputDown(ImGuiKey_DownArrow)
AddInputDown(ImGuiKey_PageUp) AddInputDown(ImGuiKey_PageDown) AddInputDown(
ImGuiKey_Home) AddInputDown(ImGuiKey_End) AddInputDown(ImGuiKey_Insert)
AddInputDown(ImGuiKey_Delete) AddInputDown(ImGuiKey_Backspace) AddInputDown(
ImGuiKey_Space) AddInputDown(ImGuiKey_Enter) AddInputDown(ImGuiKey_Escape)
AddInputDown(ImGuiKey_LeftCtrl) AddInputDown(ImGuiKey_LeftShift) AddInputDown(
ImGuiKey_LeftAlt) AddInputDown(ImGuiKey_LeftSuper) AddInputDown(ImGuiKey_RightCtrl)
AddInputDown(ImGuiKey_RightShift) AddInputDown(ImGuiKey_RightAlt) AddInputDown(
ImGuiKey_RightSuper) AddInputDown(ImGuiKey_Menu) AddInputDown(ImGuiKey_0)
AddInputDown(ImGuiKey_1) AddInputDown(ImGuiKey_2) AddInputDown(ImGuiKey_3) AddInputDown(
ImGuiKey_4) AddInputDown(ImGuiKey_5) AddInputDown(ImGuiKey_6)
AddInputDown(ImGuiKey_7) AddInputDown(ImGuiKey_8) AddInputDown(
ImGuiKey_9) AddInputDown(ImGuiKey_A) AddInputDown(ImGuiKey_B)
AddInputDown(ImGuiKey_C) AddInputDown(ImGuiKey_D) AddInputDown(
ImGuiKey_E) AddInputDown(ImGuiKey_F) AddInputDown(ImGuiKey_G)
AddInputDown(ImGuiKey_H) AddInputDown(ImGuiKey_I) AddInputDown(
ImGuiKey_J) AddInputDown(ImGuiKey_K) AddInputDown(ImGuiKey_L)
AddInputDown(ImGuiKey_M) AddInputDown(ImGuiKey_N) AddInputDown(
ImGuiKey_O) AddInputDown(ImGuiKey_P) AddInputDown(ImGuiKey_Q)
AddInputDown(ImGuiKey_R) AddInputDown(ImGuiKey_S) AddInputDown(
ImGuiKey_T) AddInputDown(ImGuiKey_U) AddInputDown(ImGuiKey_V)
AddInputDown(ImGuiKey_W) AddInputDown(ImGuiKey_X) AddInputDown(
ImGuiKey_Y) AddInputDown(ImGuiKey_Z)
AddInputDown(ImGuiKey_F1) AddInputDown(
ImGuiKey_F2) AddInputDown(ImGuiKey_F3)
AddInputDown(ImGuiKey_F4) AddInputDown(
ImGuiKey_F5) AddInputDown(ImGuiKey_F6)
AddInputDown(ImGuiKey_F7) AddInputDown(
ImGuiKey_F8) AddInputDown(ImGuiKey_F9)
AddInputDown(ImGuiKey_F10) AddInputDown(
ImGuiKey_F11) AddInputDown(ImGuiKey_F12)
AddInputDown(ImGuiKey_Apostrophe) AddInputDown(
ImGuiKey_Comma) AddInputDown(ImGuiKey_Minus)
AddInputDown(ImGuiKey_Period) AddInputDown(
ImGuiKey_Slash) AddInputDown(ImGuiKey_Semicolon)
AddInputDown(
ImGuiKey_Equal)
AddInputDown(
ImGuiKey_LeftBracket)
AddInputDown(
ImGuiKey_Backslash)
AddInputDown(ImGuiKey_RightBracket) AddInputDown(ImGuiKey_GraveAccent) AddInputDown(ImGuiKey_CapsLock) AddInputDown(ImGuiKey_ScrollLock) AddInputDown(ImGuiKey_NumLock) AddInputDown(ImGuiKey_PrintScreen) AddInputDown(ImGuiKey_Pause) AddInputDown(ImGuiKey_Keypad0) AddInputDown(ImGuiKey_Keypad1) AddInputDown(ImGuiKey_Keypad2) AddInputDown(ImGuiKey_Keypad3) AddInputDown(ImGuiKey_Keypad4) AddInputDown(ImGuiKey_Keypad5) AddInputDown(ImGuiKey_Keypad6) AddInputDown(ImGuiKey_Keypad7)
AddInputDown(ImGuiKey_Keypad8) AddInputDown(ImGuiKey_Keypad9) AddInputDown(ImGuiKey_KeypadDecimal) AddInputDown(ImGuiKey_KeypadDivide) AddInputDown(ImGuiKey_KeypadMultiply) AddInputDown(ImGuiKey_KeypadSubtract) AddInputDown(ImGuiKey_KeypadAdd) AddInputDown(ImGuiKey_KeypadEnter)
AddInputDown(
ImGuiKey_KeypadEqual)
#if IMGUI_VERSION_NUM >= 19000
AddInputDown(
ImGuiKey_F13)
AddInputDown(
ImGuiKey_F14)
AddInputDown(
ImGuiKey_F15) AddInputDown(ImGuiKey_F16) AddInputDown(ImGuiKey_F17) AddInputDown(ImGuiKey_F18) AddInputDown(ImGuiKey_F19) AddInputDown(ImGuiKey_F20) AddInputDown(ImGuiKey_F21) AddInputDown(ImGuiKey_F22) AddInputDown(ImGuiKey_F23) AddInputDown(ImGuiKey_F24)
AddInputDown(
ImGuiKey_AppBack)
AddInputDown(
ImGuiKey_AppForward)
#endif
// Gamepad
AddInputDown(ImGuiKey_GamepadStart) AddInputDown(
ImGuiKey_GamepadBack) AddInputDown(ImGuiKey_GamepadFaceUp)
AddInputDown(ImGuiKey_GamepadFaceDown) AddInputDown(
ImGuiKey_GamepadFaceLeft) AddInputDown(ImGuiKey_GamepadFaceRight)
AddInputDown(ImGuiKey_GamepadDpadUp) AddInputDown(
ImGuiKey_GamepadDpadDown) AddInputDown(ImGuiKey_GamepadDpadLeft)
AddInputDown(ImGuiKey_GamepadDpadRight) AddInputDown(
ImGuiKey_GamepadL1) AddInputDown(ImGuiKey_GamepadR1)
AddInputDown(ImGuiKey_GamepadL2) AddInputDown(
ImGuiKey_GamepadR2) AddInputDown(ImGuiKey_GamepadL3)
AddInputDown(ImGuiKey_GamepadR3) AddAnalogInputDown(
ImGuiKey_GamepadLStickUp)
AddAnalogInputDown(ImGuiKey_GamepadLStickDown) AddAnalogInputDown(
ImGuiKey_GamepadLStickLeft)
AddAnalogInputDown(
ImGuiKey_GamepadLStickRight)
AddAnalogInputDown(
ImGuiKey_GamepadRStickUp)
AddAnalogInputDown(
ImGuiKey_GamepadRStickDown)
AddAnalogInputDown(
ImGuiKey_GamepadRStickLeft)
AddAnalogInputDown(
ImGuiKey_GamepadRStickRight)
#undef AddInputDown
#undef AddAnalogInputDown
#if IMGUI_VERSION_NUM < 18837
#undef ImGuiKey
#endif
#if IMGUI_VERSION_NUM < 18837
#define ImGuiMod_Ctrl ImGuiKey_ModCtrl
#define ImGuiMod_Shift ImGuiKey_ModShift
#define ImGuiMod_Alt ImGuiKey_ModAlt
#define ImGuiMod_Super ImGuiKey_ModSuper
#endif
io.AddKeyEvent(
ImGuiMod_Ctrl,
pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::
ImGuiKey_ReservedForModCtrl));
io.AddKeyEvent(ImGuiMod_Shift,
pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModShift));
io.AddKeyEvent(ImGuiMod_Alt,
pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModAlt));
io.AddKeyEvent(ImGuiMod_Super,
pCmdInput->IsKeyDown(
CmdInput::NetImguiKeys::ImGuiKey_ReservedForModSuper));
// Mouse
io.AddMouseWheelEvent(wheelX, wheelY);
io.AddMousePosEvent(pCmdInput->mMousePos[0], pCmdInput->mMousePos[1]);
for (int i(0); i < CmdInput::NetImguiMouseButton::ImGuiMouseButton_COUNT;
++i) {
uint64_t valMask = 0x0000000000000001ull << i;
if ((pCmdInput->mMouseDownMask ^
client.mPreviousInputState.mMouseDownMask) &
valMask) {
io.AddMouseButtonEvent(i, pCmdInput->mMouseDownMask & valMask);
}
}
#endif
uint16_t character;
io.InputQueueCharacters.resize(0);
while (client.mPendingKeyIn.ReadData(&character)) {
ImWchar ConvertedKey = static_cast<ImWchar>(character);
io.AddInputCharacter(ConvertedKey);
}
static_assert(sizeof(client.mPreviousInputState.mInputDownMask) ==
sizeof(pCmdInput->mInputDownMask),
"Array size should match");
static_assert(sizeof(client.mPreviousInputState.mInputAnalog) ==
sizeof(pCmdInput->mInputAnalog),
"Array size should match");
memcpy(client.mPreviousInputState.mInputDownMask, pCmdInput->mInputDownMask,
sizeof(client.mPreviousInputState.mInputDownMask));
memcpy(client.mPreviousInputState.mInputAnalog, pCmdInput->mInputAnalog,
sizeof(client.mPreviousInputState.mInputAnalog));
client.mPreviousInputState.mMouseDownMask = pCmdInput->mMouseDownMask;
client.mPreviousInputState.mMouseWheelVertPrev = pCmdInput->mMouseWheelVert;
client.mPreviousInputState.mMouseWheelHorizPrev =
pCmdInput->mMouseWheelHoriz;
client.mServerCompressionEnabled = pCmdInput->mCompressionUse;
client.mServerCompressionSkip |= pCmdInput->mCompressionSkip;
}
if (hasNewInput) {
netImguiDeleteSafe(client.mpCmdInputPending);
client.mpCmdInputPending = pCmdInputNew;
}
return hasNewInput;
}
} // namespace NetImgui
#endif // NETIMGUI_ENABLED
#include "NetImgui_WarningReenable.h"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,236 @@
#pragma once
#include <mutex>
#include "NetImgui_CmdPackets.h"
#include "NetImgui_Shared.h"
//=============================================================================
// Forward Declares
//=============================================================================
namespace NetImgui {
namespace Internal {
namespace Network {
struct SocketInfo;
}
} // namespace Internal
} // namespace NetImgui
namespace NetImgui {
namespace Internal {
namespace Client {
//=============================================================================
// Keeps a list of ImGui context values NetImgui overrides (to restore)
//=============================================================================
struct SavedImguiContext {
void Save(ImGuiContext* copyFrom);
void Restore(ImGuiContext* copyTo);
const char* mBackendPlatformName = nullptr;
const char* mBackendRendererName = nullptr;
void* mImeWindowHandle = nullptr;
ImGuiBackendFlags mBackendFlags = 0;
ImGuiConfigFlags mConfigFlags = 0;
bool mDrawMouse = false;
bool mSavedContext = false;
char mPadding1[2] = {};
void* mClipboardUserData = nullptr;
#if IMGUI_VERSION_NUM < 19110
const char* (*mGetClipboardTextFn)(void*) = nullptr;
void (*mSetClipboardTextFn)(void*, const char*) = nullptr;
#else
const char* (*mGetClipboardTextFn)(ImGuiContext*) = nullptr;
void (*mSetClipboardTextFn)(ImGuiContext*, const char*) = nullptr;
#endif
#if IMGUI_VERSION_NUM < 18700
int mKeyMap[ImGuiKey_COUNT] = {};
char mPadding2[8 - (sizeof(mKeyMap) % 8)] = {};
#endif
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
float mFontGlobalScale = 1.f;
float mFontGeneratedSize = 0.f;
#endif
};
//=============================================================================
// Keep all Client infos needed for communication with server
//=============================================================================
struct ClientInfo {
using BufferKeys = Ringbuffer<uint16_t, 1024>;
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
struct InputState {
uint64_t mInputDownMask[(CmdInput::ImGuiKey_COUNT + 63) / 64] = {};
float mInputAnalog[CmdInput::kAnalog_Count] = {};
uint64_t mMouseDownMask = 0;
float mMouseWheelVertPrev = 0.f;
float mMouseWheelHorizPrev = 0.f;
};
ClientInfo();
~ClientInfo();
void ContextInitialize();
void ContextOverride();
void ContextRestore();
void ContextRemoveHooks();
inline bool IsContextOverriden() const;
inline bool IsConnected() const;
inline bool IsConnectPending() const;
inline bool IsActive() const;
bool TextureTrackingAdd(CmdTexture& cmdTexture);
bool TextureTrackingRem(ClientTextureID cmdTexture);
void TextureTrackingClear();
void TextureTrackingUpdate(
bool bResendAll = false); // Process Backend ImGui textures
CmdTexture* TextureCmdAllocate(ClientTextureID clientTexID, uint16_t width,
uint16_t height, eTexFormat format,
uint32_t& dataSizeInOut);
void TexturePendingServerAdd(
CmdTexture& cmdTexture); // Add CmdTexture to list of command waiting for
// send off to Server
void ProcessDrawData(const ImDrawData* pDearImguiData,
ImGuiMouseCursor mouseCursor);
std::atomic<Network::SocketInfo*>
mpSocketPending; // Hold socket info until communication is established
std::atomic<Network::SocketInfo*>
mpSocketComs; // Socket used for communications with server
std::atomic<Network::SocketInfo*>
mpSocketListen; // Socket used to wait for communication request from
// server
std::atomic_bool mbDisconnectPending; // Terminate Client/Server coms
std::atomic_bool
mbDisconnectListen; // Terminate waiting connection from Server
uint32_t mSocketListenPort = 0; // Socket Port number used to wait for
// communication request from server
char mName[64] = {};
uint64_t mFrameIndex =
0; // Incremented every time we send a DrawFrame Command
std::mutex mPendingTexturesLock; // Lock to prevent thread contention on the
// list of texure cmd waiting to be sent to
// the NetImgui Server
CmdTexture* mPendingTextures =
nullptr; // List of texture commands waiting to be send to Sever (single
// linked list with oldest item at the head)
ImVector<CmdTexture*>
mTrackedTextures; // List of texture commands to create textures used by
// this client (note for large texture count, should be
// replace with a unordered_map for fast operations)
ExchangePtr<CmdDrawFrame> mPendingFrameOut;
ExchangePtr<CmdBackground> mPendingBackgroundOut;
ExchangePtr<CmdInput> mPendingInputIn;
ExchangePtr<CmdClipboard>
mPendingClipboardIn; // Clipboard content received from Server and
// waiting to be taken by client
ExchangePtr<CmdClipboard>
mPendingClipboardOut; // Clipboard content copied on Client and waiting
// to be sent to Server
ImGuiContext* mpContext =
nullptr; // Context that the remote drawing should use (the one active
// when connection request happened)
PendingCom mPendingRcv; // Data being currently received from Server
PendingCom mPendingSend; // Data being currently sent to Server
CmdPendingRead mCmdPendingRead; // Used to get info on the next incoming
// command from Server
CmdInput* mpCmdInputPending = nullptr; // Last Input Command from server,
// waiting to be processed by client
CmdClipboard* mpCmdClipboard = nullptr; // Last received clipboad command
CmdDrawFrame* mpCmdDrawLast =
nullptr; // Last sent Draw Command. Used by data compression, to generate
// delta between previous and current frame
CmdBackground
mBGSetting; // Current value assigned to background appearance by user
CmdBackground mBGSettingSent; // Last sent value to remote server
BufferKeys
mPendingKeyIn; // Keys pressed received. Results of 2 CmdInputs are
// concatenated if received before being processed
TimePoint mLastOutgoingDrawCheckTime; // When we last checked if we have a
// pending draw command to send
TimePoint mLastOutgoingDrawTime; // When we last sent an updated draw command
// to the server
ImVec2 mSavedDisplaySize = {
0, 0}; // Save original display size on 'NewFrame' and restore it on
// 'EndFrame' (making sure size is still valid after a disconnect)
SavedImguiContext mSavedContextValues; // Oiginal ImGui context values that
// will be restored on disconnect
std::atomic_bool mbClientThreadActive; // True when connected and
// communicating with Server
std::atomic_bool mbListenThreadActive; // True when listening from connection
// request from Server
std::atomic_bool
mbComInitActive; // True when attempting to initialize a new connection
bool mbTrackedTexturesPending =
false; // True if there are some pending tracked textures waiting to be
// removed
bool mbIsDrawing =
false; // We are inside a 'NetImgui::NewFrame' / 'NetImgui::EndFrame'
// (even if not for a remote draw)
bool mbIsRemoteDrawing =
false; // True if the rendering it meant for the remote netImgui server
bool mbRestorePending =
false; // Original context has had some settings overridden, original
// values stored in mRestoreXXX
bool mbInsideHook = false; // Currently inside ImGui hook callback
bool mbInsideNewEnd =
false; // Currently inside NetImgui::NewFrame() or NetImgui::EndFrame()
// (prevents recusrive hook call)
bool mbValidDrawFrame = false; // If we should forward the drawdata to the
// server at the end of ImGui::Render()
uint8_t mClientCompressionMode = eCompressionMode::kUseServerSetting;
bool mServerCompressionEnabled =
false; // If Server would like compression to be enabled
// (mClientCompressionMode value can override this value)
bool mServerCompressionSkip =
false; // Force ignore compression setting for 1 frame
bool mServerForceConnectEnabled =
true; // If another NetImguiServer can take connection away from the one
// currently active
ThreadFunctPtr mThreadFunction =
nullptr; // Function to use when laucnhing new threads
float mFontSavedScaling = 0.f; // Original Font scaling before our override
// between NewFrame / EndFrame
float mFontServerScale =
1.f; // Desired Font DPI Scaling by the NetImgui Server
float mDesiredFps = 30.f; // How often we should update the remote drawing.
// Received from server
InputState mPreviousInputState; // Keeping track of last keyboard/mouse state
ImGuiID mhImguiHookNewframe = 0;
ImGuiID mhImguiHookEndframe = 0;
int mClientTextureIDNext =
0; // Next available ID to assign to new Dear ImGui managed textures
int mDearImguiTextureCount =
0; // Keep track of number of Dear ImGui managed texture added (to detect
// when DearImgui released some)
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
const void* mpFontTextureData =
nullptr; // Last font texture data send to server (used to detect if font
// was changed)
uint64_t mFontTextureID =
0; // Used to detect textureID change [Before ImGui 1.92, old Font Atlas]
FontCreateFuncPtr mFontCreationFunction =
nullptr; // Method to call to generate the remote ImGui font. By default,
// re-use the local font, but this doesn't handle native DPI
// scaling on remote server. //NOTE: Unused by Dear imGui 1.92+
bool mbFontUploaded = false; // Auto detect if font was sent to server
#endif
// Prevent warnings about implicitly created copy
protected:
ClientInfo(const ClientInfo&) = delete;
ClientInfo(const ClientInfo&&) = delete;
void operator=(const ClientInfo&) = delete;
};
//=============================================================================
// Main communication loop threads that are run in separate threads
//=============================================================================
void CommunicationsConnect(void* pClientVoid);
void CommunicationsHost(void* pClientVoid);
} // namespace Client
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_Client.inl"
@@ -0,0 +1,26 @@
#include "NetImgui_Network.h"
namespace NetImgui { namespace Internal { namespace Client {
bool ClientInfo::IsConnected()const
{
return mpSocketComs.load() != nullptr;
}
bool ClientInfo::IsConnectPending()const
{
return mbComInitActive || mpSocketPending.load() != nullptr || mpSocketListen.load() != nullptr;
}
bool ClientInfo::IsActive()const
{
return mbClientThreadActive || mbListenThreadActive;
}
bool ClientInfo::IsContextOverriden()const
{
return mSavedContextValues.mSavedContext;
}
}}} // namespace NetImgui::Internal::Client
@@ -0,0 +1,426 @@
#pragma once
#include "NetImgui_CmdPackets_DrawFrame.h"
#include "NetImgui_Shared.h"
namespace NetImgui {
namespace Internal {
// Note: If updating any of these commands data structure, increase
// 'CmdVersion::eVersion'
struct alignas(8) CmdHeader {
enum class eCommands : uint8_t {
Version,
Texture,
Input,
DrawFrame,
Background,
Clipboard,
Count
};
CmdHeader(eCommands CmdType, uint16_t Size) : mSize(Size), mType(CmdType) {}
uint32_t mSize = 0;
eCommands mType = eCommands::Count;
uint8_t mSent =
false; // True when command finished being sent to client or server
uint8_t mPadding[2] = {};
};
// Used as step 1 of 2 of reading incoming transmission between Client/Server,
// to get header whose size we know
struct alignas(8) CmdPendingRead : public CmdHeader {
CmdPendingRead() : CmdHeader(eCommands::Count, sizeof(CmdPendingRead)) {}
};
struct alignas(8) CmdVersion : public CmdHeader {
enum class eVersion : uint32_t {
Initial = 1,
NewTextureFormat = 2,
ImguiVersionInfo =
3, // Added Dear Imgui/ NetImgui version info to 'CmdVersion'
ServerRefactor = 4, // Change to 'CmdInput' and 'CmdVersion' store size of
// 'ImWchar' to make sure they are compatible
BackgroundCmd = 5, // Added new command to control background appearance
ClientName =
6, // Increase maximum allowed client name that a program can set
DataCompression = 7, // Adding support for data compression between
// client/server. Simple low cost delta compressor
// (only send difference from previous frame)
DataCompression2 = 8, // Improvement to data compression (save corner
// position and use SoA for vertices data)
VertexUVRange = 9, // Changed vertices UV value range to [0,1] for
// increased precision on large font texture
Imgui_1_87 = 10, // Added Dear ImGui Input refactor
OffetPointer =
11, // Updated the handling of OffsetPoint. Moved flag bit from last
// bit to first bit. Addresses and data are always at least 4 bytes
// aligned, so should never conflict with potential address space
CustomTexture = 12, // Added a 'custom' texture format to let user
// potentially handle their how format
DPIScale = 13, // Server now handle monitor DPI
Clipboard = 14, // Added clipboard support between server/client
ForceReconnect =
15, // Server can now take over the connection from another server
UpdatedComs = 16, // Faster protocol by removing blocking coms
RemDisconnect = 17, // Removed Disconnect command
ManagedTextures = 18, // Adding support for Dear Imgui Managed Textures
// (introduced in 1.92))
// Insert new version here
//--------------------------------
_count,
_current = _count - 1
};
enum class eFlags : uint8_t {
IsUnavailable = 0x01, // Client telling Server it cannot be used
IsConnected =
0x02, // Client telling Server there's already a valid connection (can
// potentially be taken over if !IsUnavailable)
ConnectForce = 0x04, // Server telling Client it want to take over
// connection if there's already one
ConnectExclusive = 0x08, // Server telling Client that once connected,
// others servers should be denied access
};
CmdVersion() : CmdHeader(CmdHeader::eCommands::Version, sizeof(CmdVersion)) {}
char mClientName[64] = {};
char mImguiVerName[16] = {IMGUI_VERSION};
char mNetImguiVerName[16] = {NETIMGUI_VERSION};
eVersion mVersion = eVersion::_current;
uint32_t mImguiVerID = IMGUI_VERSION_NUM;
uint32_t mNetImguiVerID = NETIMGUI_VERSION_NUM;
uint8_t mWCharSize = static_cast<uint8_t>(sizeof(ImWchar));
uint8_t mFlags = 0;
uint8_t PADDING[2] = {};
};
struct alignas(8) CmdInput : public CmdHeader {
// Identify a mouse button.
// Those values are guaranteed to be stable and we frequently use 0/1
// directly. Named enums provided for convenience.
enum NetImguiMouseButton {
ImGuiMouseButton_Left = 0,
ImGuiMouseButton_Right = 1,
ImGuiMouseButton_Middle = 2,
ImGuiMouseButton_Extra1 = 3, // Additional entry
ImGuiMouseButton_Extra2 = 4, // Additional entry
ImGuiMouseButton_COUNT = 5
};
// Copy of Dear ImGui key enum
// We keep our own internal version, to make sure Client key is the same as
// Server Key (since they can have different Imgui version)
enum NetImguiKeys {
// Keyboard
ImGuiKey_Tab,
ImGuiKey_LeftArrow,
ImGuiKey_RightArrow,
ImGuiKey_UpArrow,
ImGuiKey_DownArrow,
ImGuiKey_PageUp,
ImGuiKey_PageDown,
ImGuiKey_Home,
ImGuiKey_End,
ImGuiKey_Insert,
ImGuiKey_Delete,
ImGuiKey_Backspace,
ImGuiKey_Space,
ImGuiKey_Enter,
ImGuiKey_Escape,
ImGuiKey_LeftCtrl,
ImGuiKey_LeftShift,
ImGuiKey_LeftAlt,
ImGuiKey_LeftSuper,
ImGuiKey_RightCtrl,
ImGuiKey_RightShift,
ImGuiKey_RightAlt,
ImGuiKey_RightSuper,
ImGuiKey_Menu,
ImGuiKey_0,
ImGuiKey_1,
ImGuiKey_2,
ImGuiKey_3,
ImGuiKey_4,
ImGuiKey_5,
ImGuiKey_6,
ImGuiKey_7,
ImGuiKey_8,
ImGuiKey_9,
ImGuiKey_A,
ImGuiKey_B,
ImGuiKey_C,
ImGuiKey_D,
ImGuiKey_E,
ImGuiKey_F,
ImGuiKey_G,
ImGuiKey_H,
ImGuiKey_I,
ImGuiKey_J,
ImGuiKey_K,
ImGuiKey_L,
ImGuiKey_M,
ImGuiKey_N,
ImGuiKey_O,
ImGuiKey_P,
ImGuiKey_Q,
ImGuiKey_R,
ImGuiKey_S,
ImGuiKey_T,
ImGuiKey_U,
ImGuiKey_V,
ImGuiKey_W,
ImGuiKey_X,
ImGuiKey_Y,
ImGuiKey_Z,
ImGuiKey_F1,
ImGuiKey_F2,
ImGuiKey_F3,
ImGuiKey_F4,
ImGuiKey_F5,
ImGuiKey_F6,
ImGuiKey_F7,
ImGuiKey_F8,
ImGuiKey_F9,
ImGuiKey_F10,
ImGuiKey_F11,
ImGuiKey_F12,
ImGuiKey_F13,
ImGuiKey_F14,
ImGuiKey_F15,
ImGuiKey_F16,
ImGuiKey_F17,
ImGuiKey_F18,
ImGuiKey_F19,
ImGuiKey_F20,
ImGuiKey_F21,
ImGuiKey_F22,
ImGuiKey_F23,
ImGuiKey_F24,
ImGuiKey_Apostrophe, // '
ImGuiKey_Comma, // ,
ImGuiKey_Minus, // -
ImGuiKey_Period, // .
ImGuiKey_Slash, // /
ImGuiKey_Semicolon, // ;
ImGuiKey_Equal, // =
ImGuiKey_LeftBracket, // [
ImGuiKey_Backslash, // \ (this text inhibit multiline comment caused by
// backslash)
ImGuiKey_RightBracket, // ]
ImGuiKey_GraveAccent, // `
ImGuiKey_CapsLock,
ImGuiKey_ScrollLock,
ImGuiKey_NumLock,
ImGuiKey_PrintScreen,
ImGuiKey_Pause,
ImGuiKey_Keypad0,
ImGuiKey_Keypad1,
ImGuiKey_Keypad2,
ImGuiKey_Keypad3,
ImGuiKey_Keypad4,
ImGuiKey_Keypad5,
ImGuiKey_Keypad6,
ImGuiKey_Keypad7,
ImGuiKey_Keypad8,
ImGuiKey_Keypad9,
ImGuiKey_KeypadDecimal,
ImGuiKey_KeypadDivide,
ImGuiKey_KeypadMultiply,
ImGuiKey_KeypadSubtract,
ImGuiKey_KeypadAdd,
ImGuiKey_KeypadEnter,
ImGuiKey_KeypadEqual,
ImGuiKey_AppBack, // Available on some keyboard/mouses. Often referred as
// "Browser Back"
ImGuiKey_AppForward,
ImGuiKey_Oem102, // Non-US backslash.
// // XBOX | SWITCH | PLAYSTA. | ->
// ACTION
ImGuiKey_GamepadStart, // Menu | + | Options |
ImGuiKey_GamepadBack, // View | - | Share |
ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle
// Menu. Hold: Windowing mode (Focus/Move/Resize
// windows)
ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel /
// Close / Exit
ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input /
// On-screen Keyboard
ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate /
// Open / Toggle / Tweak
ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move /
// Tweak / Resize Window (in Windowing mode)
ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move /
// Tweak / Resize Window (in Windowing mode)
ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak
// / Resize Window (in Windowing mode)
ImGuiKey_GamepadDpadDown, // D-pad Down | " | " | Move /
// Tweak / Resize Window (in Windowing mode)
ImGuiKey_GamepadL1, // L Bumper | L | L1 | Tweak Slower /
// Focus Previous (in Windowing mode)
ImGuiKey_GamepadR1, // R Bumper | R | R1 | Tweak Faster /
// Focus Next (in Windowing mode)
ImGuiKey_GamepadL2, // L Trigger | ZL | L2 | [Analog]
ImGuiKey_GamepadR2, // R Trigger | ZR | R2 | [Analog]
ImGuiKey_GamepadL3, // L Stick | L3 | L3 |
ImGuiKey_GamepadR3, // R Stick | R3 | R3 |
ImGuiKey_GamepadLStickLeft, // | | | [Analog]
// Move Window (in Windowing mode)
ImGuiKey_GamepadLStickRight, // | | | [Analog]
// Move Window (in Windowing mode)
ImGuiKey_GamepadLStickUp, // | | | [Analog]
// Move Window (in Windowing mode)
ImGuiKey_GamepadLStickDown, // | | | [Analog]
// Move Window (in Windowing mode)
ImGuiKey_GamepadRStickLeft, // | | | [Analog]
ImGuiKey_GamepadRStickRight, // | | | [Analog]
ImGuiKey_GamepadRStickUp, // | | | [Analog]
ImGuiKey_GamepadRStickDown, // | | | [Analog]
// Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls)
// - This is mirroring the data also written to io.MouseDown[],
// io.MouseWheel, in a format allowing them to be accessed via standard key
// API.
ImGuiKey_MouseLeft,
ImGuiKey_MouseRight,
ImGuiKey_MouseMiddle,
ImGuiKey_MouseX1,
ImGuiKey_MouseX2,
ImGuiKey_MouseWheelX,
ImGuiKey_MouseWheelY,
// Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent()
// calls)
// - This is mirroring the data also written to io.KeyCtrl, io.KeyShift,
// io.KeyAlt, io.KeySuper, in a format allowing
// them to be accessed via standard key API, allowing calls such as
// IsKeyPressed(), IsKeyReleased(), querying duration etc.
// - Code polling every keys (e.g. an interface to detect a key press for
// input mapping) might want to ignore those
// and prefer using the real keys (e.g. ImGuiKey_LeftCtrl,
// ImGuiKey_RightCtrl instead of ImGuiKey_ModCtrl).
// - In theory the value of keyboard modifiers should be roughly equivalent
// to a logical or of the equivalent left/right keys.
// In practice: it's complicated; mods are often provided from different
// sources. Keyboard layout, IME, sticky keys and backends tend to
// interfere and break that equivalence. The safer decision is to relay
// that ambiguity down to the end-user...
ImGuiKey_ReservedForModCtrl,
ImGuiKey_ReservedForModShift,
ImGuiKey_ReservedForModAlt,
ImGuiKey_ReservedForModSuper,
// End of list
ImGuiKey_COUNT, // No valid ImGuiKey is ever greater than this value
};
static constexpr uint32_t kAnalog_First = ImGuiKey_GamepadLStickLeft;
static constexpr uint32_t kAnalog_Last = ImGuiKey_GamepadRStickDown;
static constexpr uint32_t kAnalog_Count = kAnalog_Last - kAnalog_First + 1;
CmdInput() : CmdHeader(CmdHeader::eCommands::Input, sizeof(CmdInput)) {}
uint16_t mScreenSize[2] = {};
int16_t mMousePos[2] = {};
float mMouseWheelVert = 0.f;
float mMouseWheelHoriz = 0.f;
uint16_t mKeyChars[256] = {}; // Input characters
uint16_t mKeyCharCount = 0; // Number of valid input characters
bool mCompressionUse =
false; // Server would like client to compress the communication data
bool mCompressionSkip =
false; // Server forcing next client's frame data to be uncompressed
float mFontDPIScaling =
1.f; // Font scaling request by Server accounting for monitor DPI
float mDesiredFps = 30.f; // Requested redraw speed
uint64_t mMouseDownMask = 0;
uint64_t mInputDownMask[(ImGuiKey_COUNT + 63) / 64] = {};
float mInputAnalog[kAnalog_Count] = {};
inline bool IsKeyDown(NetImguiKeys netimguiKey) const;
};
struct alignas(8) CmdTexture : public CmdHeader {
enum class eType : uint8_t { Create, Update, Destroy };
CmdTexture() : CmdHeader(CmdHeader::eCommands::Texture, sizeof(CmdTexture)) {}
ClientTextureID mTextureClientID = 0;
eType mStatus = eType::Create;
uint8_t mFormat = eTexFormat::kTexFmt_Invalid; // eTexFormat
uint8_t mUpdatable = false; // Set to true on Create, for updatable textures
uint8_t mIsDearImGuiManaged =
false; // True if this is not an user created/managed texture
uint16_t mWidth =
0; // Either the texture width on create, or the update area width
uint16_t mHeight =
0; // Either the texture height on create, or the update area height
uint16_t mOffsetX = 0; // Used by partial update
uint16_t mOffsetY = 0; // Used by partial update
uint8_t PADDING[4] = {};
alignas(8) CmdTexture* mpNext =
nullptr; // Used for single linked list of pending textures (alignas
// needed to keep class size the same between win32/x64)
OffsetPointer<uint8_t> mpTextureData;
};
struct alignas(8) CmdDrawFrame : public CmdHeader {
CmdDrawFrame()
: CmdHeader(CmdHeader::eCommands::DrawFrame, sizeof(CmdDrawFrame)) {}
uint64_t mFrameIndex = 0;
uint32_t mMouseCursor = 0; // ImGuiMouseCursor value
float mDisplayArea[4] = {};
uint32_t mIndiceByteSize = 0;
uint32_t mDrawGroupCount = 0;
uint32_t mTotalVerticeCount = 0;
uint32_t mTotalIndiceCount = 0;
uint32_t mTotalDrawCount = 0;
uint32_t mUncompressedSize = 0;
uint8_t mCompressed = false;
uint8_t PADDING[3] = {};
OffsetPointer<ImguiDrawGroup> mpDrawGroups;
inline void ToPointers();
inline void ToOffsets();
};
struct alignas(8) CmdBackground : public CmdHeader {
CmdBackground()
: CmdHeader(CmdHeader::eCommands::Background, sizeof(CmdBackground)) {}
static constexpr uint64_t kDefaultTexture = ~0u;
float mClearColor[4] = {0.2f, 0.2f, 0.2f, 1.f}; // Background color
float mTextureTint[4] = {1.f, 1.f, 1.f,
0.5f}; // Tint/alpha applied to texture
uint64_t mTextureId = kDefaultTexture; // Texture rendered in background, use
// server texture by default
inline bool operator==(const CmdBackground& cmp) const;
inline bool operator!=(const CmdBackground& cmp) const;
};
struct alignas(8) CmdClipboard : public CmdHeader {
CmdClipboard()
: CmdHeader(CmdHeader::eCommands::Clipboard, sizeof(CmdClipboard)) {}
size_t mByteSize = 0;
OffsetPointer<char> mContentUTF8;
inline void ToPointers();
inline void ToOffsets();
inline static CmdClipboard* Create(const char* clipboard);
};
//=============================================================================
// Keeping track of partial incoming/outgoing transmissions
//=============================================================================
struct PendingCom {
size_t SizeCurrent = 0; // Amount of data sent or received so far
bool bAutoFree = false; // Need to free data buffer at the end of processing
bool bError = false; // If an error occurs during coms
CmdHeader* pCommand =
nullptr; // Where to store incoming data or read to send data
inline bool IsError() const { return bError; }
inline bool IsDone() const {
return IsError() || (pCommand && pCommand->mSize == SizeCurrent);
}
inline bool IsReady() const { return !IsError() && pCommand == nullptr; }
inline bool IsPending() const {
return !IsError() && !IsDone() && !IsReady();
}
};
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_CmdPackets.inl"
@@ -0,0 +1,101 @@
#include "NetImgui_CmdPackets.h"
namespace NetImgui { namespace Internal
{
void CmdDrawFrame::ToPointers()
{
if( !mpDrawGroups.IsPointer() )
{
mpDrawGroups.ToPointer();
for (uint32_t i(0); i < mDrawGroupCount; ++i) {
mpDrawGroups[i].ToPointers();
}
}
}
void CmdDrawFrame::ToOffsets()
{
if( !mpDrawGroups.IsOffset() )
{
for (uint32_t i(0); i < mDrawGroupCount; ++i) {
mpDrawGroups[i].ToOffsets();
}
mpDrawGroups.ToOffset();
}
}
void ImguiDrawGroup::ToPointers()
{
if( !mpIndices.IsPointer() ) //Safer to test the first element after CmdHeader
{
mpIndices.ToPointer();
mpVertices.ToPointer();
mpDraws.ToPointer();
}
}
void ImguiDrawGroup::ToOffsets()
{
if( !mpIndices.IsOffset() ) //Safer to test the first element after CmdHeader
{
mpIndices.ToOffset();
mpVertices.ToOffset();
mpDraws.ToOffset();
}
}
bool CmdInput::IsKeyDown( CmdInput::NetImguiKeys netimguiKey) const
{
uint32_t valIndex = netimguiKey/64;
uint64_t valMask = 0x0000000000000001ull << (netimguiKey%64);
return mInputDownMask[valIndex] & valMask;
}
bool CmdBackground::operator==(const CmdBackground& cmp)const
{
bool sameValue(true);
for(size_t i(0); i<sizeof(CmdBackground)/8; i++){
sameValue &= reinterpret_cast<const uint64_t*>(this)[i] == reinterpret_cast<const uint64_t*>(&cmp)[i];
}
return sameValue;
}
bool CmdBackground::operator!=(const CmdBackground& cmp)const
{
return (*this == cmp) == false;
}
void CmdClipboard::ToPointers()
{
if( !mContentUTF8.IsPointer() ){
mContentUTF8.ToPointer();
}
}
void CmdClipboard::ToOffsets()
{
if( !mContentUTF8.IsOffset() ){
mContentUTF8.ToOffset();
}
}
CmdClipboard* CmdClipboard::Create(const char* clipboard)
{
if( clipboard )
{
size_t clipboardByteSize(0);
while(clipboard[clipboardByteSize++] != 0);
size_t totalDataCount = sizeof(CmdClipboard) + DivUp<size_t>(clipboardByteSize, ComDataSize);
auto pNewClipboard = NetImgui::Internal::netImguiSizedNew<CmdClipboard>(totalDataCount*ComDataSize);
pNewClipboard->mSize = static_cast<uint32_t>(totalDataCount*ComDataSize);
pNewClipboard->mByteSize = clipboardByteSize;
pNewClipboard->mContentUTF8.SetPtr(reinterpret_cast<char*>(&pNewClipboard[1]));
memcpy(pNewClipboard->mContentUTF8.Get(), clipboard, clipboardByteSize);
return pNewClipboard;
}
return nullptr;
}
}} // namespace NetImgui::Internal
@@ -0,0 +1,477 @@
#include "NetImgui_Shared.h"
#if NETIMGUI_ENABLED
#include "NetImgui_CmdPackets.h"
#include "NetImgui_WarningDisable.h"
namespace NetImgui {
namespace Internal {
template <typename TType>
inline void SetAndIncreaseDataPointer(OffsetPointer<TType>& dataPointer,
uint32_t dataSize,
ComDataType*& pDataOutput) {
dataPointer.SetComDataPtr(pDataOutput);
const size_t dataCount = DivUp<size_t>(dataSize, ComDataSize);
pDataOutput[dataCount - 1] = 0;
pDataOutput += dataCount;
}
//=============================================================================
// Safely convert a pointer to a int value, even if int storage size > pointer
//=============================================================================
template <typename TInt, typename TPointer>
TInt PointerCast(TPointer* pointer) {
union CastHelperUnion {
TInt ValueInt;
TPointer* ValuePointer;
};
CastHelperUnion helperObject = {};
helperObject.ValuePointer = pointer;
return helperObject.ValueInt;
}
//=================================================================================================
//
//=================================================================================================
inline void ImGui_ExtractIndices(const ImDrawList& cmdList,
ImguiDrawGroup& drawGroupOut,
ComDataType*& pDataOutput) {
bool is16Bit =
sizeof(ImDrawIdx) == 2 ||
cmdList.VtxBuffer.size() <=
0xFFFF; // When Dear Imgui is compiled with ImDrawIdx = uint16, we
// know for certain that there won't be any drawcall with
// index > 65k, even if Vertex buffer is bigger than 65k.
drawGroupOut.mBytePerIndex = is16Bit ? 2 : 4;
drawGroupOut.mIndiceCount = static_cast<uint32_t>(cmdList.IdxBuffer.size());
uint32_t sizeNeeded = drawGroupOut.mIndiceCount * drawGroupOut.mBytePerIndex;
SetAndIncreaseDataPointer(drawGroupOut.mpIndices, sizeNeeded, pDataOutput);
// No conversion needed, straight copy
if (drawGroupOut.mBytePerIndex == sizeof(ImDrawIdx)) {
memcpy(drawGroupOut.mpIndices.Get(), &cmdList.IdxBuffer.front(),
sizeNeeded);
}
// From 32bits to 16bits
else if (is16Bit) {
for (int i(0); i < static_cast<int>(drawGroupOut.mIndiceCount); ++i)
reinterpret_cast<uint16_t*>(drawGroupOut.mpIndices.Get())[i] =
static_cast<uint16_t>(cmdList.IdxBuffer[i]);
}
// From 16bits to 32bits
else {
for (int i(0); i < static_cast<int>(drawGroupOut.mIndiceCount); ++i)
reinterpret_cast<uint32_t*>(drawGroupOut.mpIndices.Get())[i] =
static_cast<uint32_t>(cmdList.IdxBuffer[i]);
}
}
//=================================================================================================
//
//=================================================================================================
inline void ImGui_ExtractVertices(const ImDrawList& cmdList,
ImguiDrawGroup& drawGroupOut,
ComDataType*& pDataOutput) {
drawGroupOut.mVerticeCount = static_cast<uint32_t>(cmdList.VtxBuffer.size());
drawGroupOut.mReferenceCoord[0] =
drawGroupOut.mVerticeCount > 0 ? cmdList.VtxBuffer[0].pos.x : 0.f;
drawGroupOut.mReferenceCoord[1] =
drawGroupOut.mVerticeCount > 0 ? cmdList.VtxBuffer[0].pos.y : 0.f;
SetAndIncreaseDataPointer(drawGroupOut.mpVertices,
drawGroupOut.mVerticeCount * sizeof(ImguiVert),
pDataOutput);
ImguiVert* pVertices = drawGroupOut.mpVertices.Get();
for (int i(0); i < static_cast<int>(drawGroupOut.mVerticeCount); ++i) {
const auto& Vtx = cmdList.VtxBuffer[i];
pVertices[i].mColor = Vtx.col;
pVertices[i].mUV[0] = static_cast<uint16_t>(
(Vtx.uv.x - static_cast<float>(ImguiVert::kUvRange_Min) +
0.5f / 65535.f) *
0xFFFF / (ImguiVert::kUvRange_Max - ImguiVert::kUvRange_Min));
pVertices[i].mUV[1] = static_cast<uint16_t>(
(Vtx.uv.y - static_cast<float>(ImguiVert::kUvRange_Min) +
0.5f / 65535.f) *
0xFFFF / (ImguiVert::kUvRange_Max - ImguiVert::kUvRange_Min));
pVertices[i].mPos[0] = static_cast<uint16_t>(
(Vtx.pos.x - drawGroupOut.mReferenceCoord[0] -
static_cast<float>(ImguiVert::kPosRange_Min)) *
0xFFFF / (ImguiVert::kPosRange_Max - ImguiVert::kPosRange_Min));
pVertices[i].mPos[1] = static_cast<uint16_t>(
(Vtx.pos.y - drawGroupOut.mReferenceCoord[1] -
static_cast<float>(ImguiVert::kPosRange_Min)) *
0xFFFF / (ImguiVert::kPosRange_Max - ImguiVert::kPosRange_Min));
}
}
//=================================================================================================
//
//=================================================================================================
inline void ImGui_ExtractDraws(const ImDrawList& cmdList,
ImguiDrawGroup& drawGroupOut,
ComDataType*& pDataOutput) {
int maxDrawCount = static_cast<int>(cmdList.CmdBuffer.size());
uint32_t drawCount = 0;
ImguiDraw* pOutDraws = reinterpret_cast<ImguiDraw*>(pDataOutput);
for (int cmd_i = 0; cmd_i < maxDrawCount; ++cmd_i) {
const ImDrawCmd* pCmd = &cmdList.CmdBuffer[cmd_i];
if (pCmd->UserCallback == nullptr) {
#if IMGUI_VERSION_NUM >= 17100
pOutDraws[drawCount].mVtxOffset = pCmd->VtxOffset;
pOutDraws[drawCount].mIdxOffset = pCmd->IdxOffset;
#else
pOutDraws[drawCount].mVtxOffset = 0;
pOutDraws[drawCount].mIdxOffset = 0;
#endif
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
ClientTextureID texClientID = ConvertToClientTexID(pCmd->TexRef);
#else
ClientTextureID texClientID = ConvertToClientTexID(pCmd->TextureId);
#endif
pOutDraws[drawCount].mClientTexId = texClientID;
pOutDraws[drawCount].mIdxCount = pCmd->ElemCount;
pOutDraws[drawCount].mClipRect[0] = pCmd->ClipRect.x;
pOutDraws[drawCount].mClipRect[1] = pCmd->ClipRect.y;
pOutDraws[drawCount].mClipRect[2] = pCmd->ClipRect.z;
pOutDraws[drawCount].mClipRect[3] = pCmd->ClipRect.w;
++drawCount;
}
}
drawGroupOut.mDrawCount = drawCount;
static_assert(sizeof(ImguiDraw) % ComDataSize == 0,
"Need to support zero-ing the pending bytes, when not a size "
"multiple of DataComType");
drawGroupOut.mpDraws.SetComDataPtr(pDataOutput);
pDataOutput += drawGroupOut.mDrawCount * sizeof(ImguiDraw) / ComDataSize;
}
//=================================================================================================
// Delta comress data.
// Take 2 data stream and output a stream with only the data difference from
// each other
//=================================================================================================
void CompressData(const ComDataType* pDataPrev, size_t dataSizePrev,
const ComDataType* pDataNew, size_t dataSizeNew,
ComDataType*& pCommandMemoryInOut) {
static_assert(sizeof(uint32_t) * 2 <= ComDataSize,
"Need to adjust compression algorithm pointer calculation");
const size_t elemCountPrev =
static_cast<size_t>(DivUp(dataSizePrev, sizeof(uint64_t)));
const size_t elemCountNew =
static_cast<size_t>(DivUp(dataSizeNew, sizeof(uint64_t)));
const size_t elemCount =
elemCountPrev < elemCountNew ? elemCountPrev : elemCountNew;
size_t n = 0;
if (pDataPrev) {
while (n < elemCount) {
uint32_t* pBlockInfo = reinterpret_cast<uint32_t*>(
pCommandMemoryInOut++); // Add a new block info to output
// Find number of elements with same value as last frame
size_t startN = n;
while (n < elemCount && pDataPrev[n] == pDataNew[n]) ++n;
pBlockInfo[0] = static_cast<uint32_t>(n - startN);
// Find number of elements with different value as last frame, and save
// new value
while (n < elemCount && pDataPrev[n] != pDataNew[n]) {
*pCommandMemoryInOut = pDataNew[n++];
++pCommandMemoryInOut;
}
pBlockInfo[1] =
static_cast<uint32_t>(pCommandMemoryInOut -
reinterpret_cast<ComDataType*>(pBlockInfo)) -
1;
}
}
// New frame has more element than previous frame, add the remaining entries
if (elemCount < elemCountNew) {
uint32_t* pBlockInfo = reinterpret_cast<uint32_t*>(
pCommandMemoryInOut++); // Add a new block info to output
while (n < elemCountNew) {
*pCommandMemoryInOut = pDataNew[n++];
++pCommandMemoryInOut;
}
pBlockInfo[0] = 0;
pBlockInfo[1] =
static_cast<uint32_t>(pCommandMemoryInOut -
reinterpret_cast<uint64_t*>(pBlockInfo)) -
1;
}
}
//=================================================================================================
// Unpack a delta data compressed stream
//=================================================================================================
void DecompressData(const ComDataType* pDataPrev, size_t dataSizePrev,
const ComDataType* pDataPack, size_t dataUnpackSize,
ComDataType*& pCommandMemoryInOut) {
const size_t elemCountPrev = DivUp(dataSizePrev, ComDataSize);
const size_t elemCountUnpack = DivUp(dataUnpackSize, ComDataSize);
const size_t elemCountCopy =
elemCountPrev < elemCountUnpack ? elemCountPrev : elemCountUnpack;
uint64_t* pCommandMemoryEnd = &pCommandMemoryInOut[elemCountUnpack];
if (pDataPrev) {
memcpy(pCommandMemoryInOut, pDataPrev, elemCountCopy * ComDataSize);
}
while (pCommandMemoryInOut < pCommandMemoryEnd) {
const uint32_t* pBlockInfo = reinterpret_cast<const uint32_t*>(
pDataPack++); // Add a new block info to output
pCommandMemoryInOut += pBlockInfo[0];
memcpy(pCommandMemoryInOut, pDataPack, pBlockInfo[1] * sizeof(uint64_t));
pCommandMemoryInOut += pBlockInfo[1];
pDataPack += pBlockInfo[1];
}
}
//=================================================================================================
// Take a regular NetImgui DrawFrame command and create a new compressed command
// It uses a basic delta compression method that works really well with Imgui
// data
// - Most of the drawing data do not change between 2 frames
// - Even if 1 window content changes, the others windows probably won't be
// changing at all
// - This means that for each window, we can only send the data that changed
// - This requires little cpu usage and generate good results
// - In 'SampleBasic' with 3 windows open (Main Window, ImGui Demo, ImGui
// Metric) at 30fps
// - Compression Off: 1650KB/sec of transfert
// - Compression On : 12KB/sec of transfert (130x less data)
//=================================================================================================
CmdDrawFrame* CompressCmdDrawFrame(const CmdDrawFrame* pDrawFramePrev,
const CmdDrawFrame* pDrawFrameNew) {
//-----------------------------------------------------------------------------------------
// Allocate memory for the new compressed command
//-----------------------------------------------------------------------------------------
// Allocate memory for worst case scenario (no compression possible)
// New DrawFrame size + 2 'compression block info' per data stream
size_t neededDataCount =
DivUp<size_t>(pDrawFrameNew->mSize, ComDataSize) +
6 * static_cast<size_t>(pDrawFrameNew->mDrawGroupCount);
CmdDrawFrame* pDrawFramePacked =
netImguiSizedNew<CmdDrawFrame>(neededDataCount * ComDataSize);
*pDrawFramePacked = *pDrawFrameNew;
pDrawFramePacked->mCompressed = true;
ComDataType* pDataOutput =
reinterpret_cast<ComDataType*>(&pDrawFramePacked[1]);
SetAndIncreaseDataPointer(
pDrawFramePacked->mpDrawGroups,
pDrawFramePacked->mDrawGroupCount * sizeof(ImguiDrawGroup), pDataOutput);
//-----------------------------------------------------------------------------------------
// Copy draw data (vertices, indices, drawcall info, ...)
//-----------------------------------------------------------------------------------------
const uint32_t groupCountPrev = pDrawFramePrev->mDrawGroupCount;
for (uint32_t n = 0; n < pDrawFramePacked->mDrawGroupCount; n++) {
// Look for the same drawgroup in previous frame
// Can usually avoid a search by checking same index in previous frame
// (drawgroup ordering shouldn't change often)
const ImguiDrawGroup& drawGroupNew = pDrawFrameNew->mpDrawGroups[n];
ImguiDrawGroup& drawGroup = pDrawFramePacked->mpDrawGroups[n];
drawGroup = drawGroupNew;
drawGroup.mDrawGroupIdxPrev =
(n < groupCountPrev &&
drawGroup.mGroupID == pDrawFramePrev->mpDrawGroups[n].mGroupID)
? n
: ImguiDrawGroup::kInvalidDrawGroup;
for (uint32_t j(0);
j < groupCountPrev &&
drawGroup.mDrawGroupIdxPrev == ImguiDrawGroup::kInvalidDrawGroup;
++j) {
drawGroup.mDrawGroupIdxPrev =
(drawGroup.mGroupID == pDrawFramePrev->mpDrawGroups[j].mGroupID)
? j
: ImguiDrawGroup::kInvalidDrawGroup;
}
// Delta compress the 3 data streams
const uint64_t *pVerticePrev(nullptr), *pIndicePrev(nullptr),
*pDrawsPrev(nullptr);
size_t verticeSizePrev(0), indiceSizePrev(0), drawSizePrev(0);
if (drawGroup.mDrawGroupIdxPrev < pDrawFramePrev->mDrawGroupCount) {
const ImguiDrawGroup& drawGroupPrev =
pDrawFramePrev->mpDrawGroups[drawGroup.mDrawGroupIdxPrev];
pVerticePrev =
reinterpret_cast<const uint64_t*>(drawGroupPrev.mpVertices.Get());
pIndicePrev =
reinterpret_cast<const uint64_t*>(drawGroupPrev.mpIndices.Get());
pDrawsPrev =
reinterpret_cast<const uint64_t*>(drawGroupPrev.mpDraws.Get());
verticeSizePrev = drawGroupPrev.mVerticeCount * sizeof(ImguiVert);
indiceSizePrev = drawGroupPrev.mIndiceCount *
static_cast<size_t>(drawGroupPrev.mBytePerIndex);
drawSizePrev = drawGroupPrev.mDrawCount * sizeof(ImguiDraw);
}
drawGroup.mpIndices.SetComDataPtr(pDataOutput);
CompressData(pIndicePrev, indiceSizePrev,
drawGroupNew.mpIndices.GetComData(),
drawGroupNew.mIndiceCount *
static_cast<size_t>(drawGroupNew.mBytePerIndex),
pDataOutput);
drawGroup.mpVertices.SetComDataPtr(pDataOutput);
CompressData(pVerticePrev, verticeSizePrev,
drawGroupNew.mpVertices.GetComData(),
drawGroupNew.mVerticeCount * sizeof(ImguiVert), pDataOutput);
drawGroup.mpDraws.SetComDataPtr(pDataOutput);
CompressData(pDrawsPrev, drawSizePrev, drawGroupNew.mpDraws.GetComData(),
drawGroupNew.mDrawCount * sizeof(ImguiDraw), pDataOutput);
}
// Adjust data transfert amount to memory that has been actually needed
pDrawFramePacked->mSize =
static_cast<uint32_t>(
(pDataOutput - reinterpret_cast<ComDataType*>(pDrawFramePacked))) *
static_cast<uint32_t>(sizeof(uint64_t));
return pDrawFramePacked;
}
//=================================================================================================
//
//=================================================================================================
CmdDrawFrame* DecompressCmdDrawFrame(const CmdDrawFrame* pDrawFramePrev,
const CmdDrawFrame* pDrawFramePacked) {
//-----------------------------------------------------------------------------------------
// Allocate memory for the new uncompressed compressed command
//-----------------------------------------------------------------------------------------
CmdDrawFrame* pDrawFrameNew =
netImguiSizedNew<CmdDrawFrame>(pDrawFramePacked->mUncompressedSize);
*pDrawFrameNew = *pDrawFramePacked;
pDrawFrameNew->mCompressed = false;
ComDataType* pDataOutput = reinterpret_cast<ComDataType*>(&pDrawFrameNew[1]);
SetAndIncreaseDataPointer(
pDrawFrameNew->mpDrawGroups,
pDrawFrameNew->mDrawGroupCount * sizeof(ImguiDrawGroup), pDataOutput);
for (uint32_t n = 0; n < pDrawFrameNew->mDrawGroupCount; n++) {
const ImguiDrawGroup& drawGroupPack = pDrawFramePacked->mpDrawGroups[n];
ImguiDrawGroup& drawGroup = pDrawFrameNew->mpDrawGroups[n];
drawGroup = drawGroupPack;
// Uncompress the 3 data streams
const ComDataType* pVerticePrev = nullptr;
const ComDataType* pIndicePrev = nullptr;
const ComDataType* pDrawsPrev = nullptr;
size_t verticeSizePrev(0), indiceSizePrev(0), drawSizePrev(0);
if (drawGroup.mDrawGroupIdxPrev < pDrawFramePrev->mDrawGroupCount) {
const ImguiDrawGroup& drawGroupPrev =
pDrawFramePrev->mpDrawGroups[drawGroup.mDrawGroupIdxPrev];
pVerticePrev =
reinterpret_cast<const ComDataType*>(drawGroupPrev.mpVertices.Get());
pIndicePrev =
reinterpret_cast<const ComDataType*>(drawGroupPrev.mpIndices.Get());
pDrawsPrev =
reinterpret_cast<const ComDataType*>(drawGroupPrev.mpDraws.Get());
verticeSizePrev = drawGroupPrev.mVerticeCount * sizeof(ImguiVert);
indiceSizePrev = drawGroupPrev.mIndiceCount *
static_cast<size_t>(drawGroupPrev.mBytePerIndex);
drawSizePrev = drawGroupPrev.mDrawCount * sizeof(ImguiDraw);
}
drawGroup.mpIndices.SetComDataPtr(pDataOutput);
DecompressData(pIndicePrev, indiceSizePrev,
drawGroupPack.mpIndices.GetComData(),
drawGroupPack.mIndiceCount *
static_cast<size_t>(drawGroupPack.mBytePerIndex),
pDataOutput);
drawGroup.mpVertices.SetComDataPtr(pDataOutput);
DecompressData(
pVerticePrev, verticeSizePrev, drawGroupPack.mpVertices.GetComData(),
drawGroupPack.mVerticeCount * sizeof(ImguiVert), pDataOutput);
drawGroup.mpDraws.SetComDataPtr(pDataOutput);
DecompressData(pDrawsPrev, drawSizePrev, drawGroupPack.mpDraws.GetComData(),
drawGroupPack.mDrawCount * sizeof(ImguiDraw), pDataOutput);
}
return pDrawFrameNew;
}
//=================================================================================================
// Take a regular Dear Imgui Draw Data, and convert it to a NetImgui DrawFrame
// Command It involves saving each window draw group vertex/indices/draw buffers
// and packing their data a little bit, to reduce the bandwidth usage
//=================================================================================================
CmdDrawFrame* ConvertToCmdDrawFrame(const ImDrawData* pDearImguiData,
ImGuiMouseCursor mouseCursor) {
//-----------------------------------------------------------------------------------------
// Find memory needed for entire DrawFrame Command
//-----------------------------------------------------------------------------------------
static_assert(sizeof(CmdDrawFrame) % ComDataSize == 0,
"Make sure Command Data is aligned to com data type size");
size_t neededDataCount = DivUp(sizeof(CmdDrawFrame), ComDataSize);
neededDataCount += DivUp(static_cast<size_t>(pDearImguiData->CmdListsCount) *
sizeof(ImguiDrawGroup),
ComDataSize);
for (int n = 0; n < pDearImguiData->CmdListsCount; n++) {
const ImDrawList* pCmdList = pDearImguiData->CmdLists[n];
bool is16Bit = pCmdList->VtxBuffer.size() <= 0xFFFF;
neededDataCount += DivUp(
static_cast<size_t>(pCmdList->VtxBuffer.size()) * sizeof(ImguiVert),
ComDataSize);
neededDataCount += DivUp(
static_cast<size_t>(pCmdList->IdxBuffer.size()) * (is16Bit ? 2 : 4),
ComDataSize);
neededDataCount += DivUp(
static_cast<size_t>(pCmdList->CmdBuffer.size()) * sizeof(ImguiDraw),
ComDataSize);
}
//-----------------------------------------------------------------------------------------
// Allocate Data and initialize general frame information
//-----------------------------------------------------------------------------------------
CmdDrawFrame* pDrawFrame =
netImguiSizedNew<CmdDrawFrame>(neededDataCount * ComDataSize);
ComDataType* pDataOutput = reinterpret_cast<ComDataType*>(&pDrawFrame[1]);
pDrawFrame->mMouseCursor = static_cast<uint32_t>(mouseCursor);
pDrawFrame->mDisplayArea[0] = pDearImguiData->DisplayPos.x;
pDrawFrame->mDisplayArea[1] = pDearImguiData->DisplayPos.y;
pDrawFrame->mDisplayArea[2] =
pDearImguiData->DisplayPos.x + pDearImguiData->DisplaySize.x;
pDrawFrame->mDisplayArea[3] =
pDearImguiData->DisplayPos.y + pDearImguiData->DisplaySize.y;
pDrawFrame->mDrawGroupCount =
static_cast<uint32_t>(pDearImguiData->CmdListsCount);
SetAndIncreaseDataPointer(pDrawFrame->mpDrawGroups,
static_cast<uint32_t>(pDrawFrame->mDrawGroupCount *
sizeof(ImguiDrawGroup)),
pDataOutput);
//-----------------------------------------------------------------------------------------
// Copy draw data (vertices, indices, drawcall info, ...)
//-----------------------------------------------------------------------------------------
for (size_t n = 0; n < pDrawFrame->mDrawGroupCount; n++) {
ImguiDrawGroup& drawGroup = pDrawFrame->mpDrawGroups[n];
const ImDrawList* pCmdList = pDearImguiData->CmdLists[static_cast<int>(n)];
drawGroup = ImguiDrawGroup();
drawGroup.mGroupID = PointerCast<uint64_t>(
pCmdList->_OwnerName); // Use the name string pointer as a unique ID
// (seems to remain the same between frame)
ImGui_ExtractIndices(*pCmdList, drawGroup, pDataOutput);
ImGui_ExtractVertices(*pCmdList, drawGroup, pDataOutput);
ImGui_ExtractDraws(*pCmdList, drawGroup, pDataOutput);
pDrawFrame->mTotalVerticeCount += drawGroup.mVerticeCount;
pDrawFrame->mTotalIndiceCount += drawGroup.mIndiceCount;
pDrawFrame->mTotalDrawCount += drawGroup.mDrawCount;
}
pDrawFrame->mSize =
static_cast<uint32_t>(pDataOutput -
reinterpret_cast<const ComDataType*>(pDrawFrame)) *
ComDataSize;
pDrawFrame->mUncompressedSize =
pDrawFrame->mSize; // No compression with this item, so same value
return pDrawFrame;
}
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_WarningReenable.h"
#endif // #if NETIMGUI_ENABLED
@@ -0,0 +1,63 @@
#pragma once
#include "NetImgui_Shared.h"
namespace NetImgui {
namespace Internal {
struct ImguiVert {
// Note: If updating this, increase 'CmdVersion::eVersion'
enum Constants {
kUvRange_Min = 0,
kUvRange_Max = 1,
kPosRange_Min = -8192,
kPosRange_Max = 8192
};
uint16_t mPos[2];
uint16_t mUV[2];
uint32_t mColor;
};
struct ImguiDraw {
ClientTextureID
mClientTexId; // TextureID used by client to identify the texture
uint32_t mIdxCount; // Drawcall number of indices (3 indices per triangles)
uint32_t mVtxOffset; // Drawcall start position in vertices buffer
// (considered index 0)
uint32_t mIdxOffset; // Drawcall start position in indices buffer
float mClipRect[4];
uint8_t PADDING[4] = {};
};
// Each DearImgui window has its own vertex/index buffers with multiple
// drawcalls
struct alignas(8) ImguiDrawGroup {
static constexpr uint32_t kInvalidDrawGroup = 0xFFFFFFFF;
uint64_t mGroupID = 0; // Unique ID to recognize DrawGroup between 2 frames
uint32_t mVerticeCount = 0;
uint32_t mIndiceCount = 0;
uint32_t mDrawCount = 0;
uint32_t mDrawGroupIdxPrev =
kInvalidDrawGroup; // Group index in previous DrawFrame
// (kInvalidDrawGroup when not using delta
// compression)
uint8_t mBytePerIndex = 2; // 2, 4 bytes
uint8_t PADDING[7] = {};
float mReferenceCoord[2] = {}; // Reference position for the encoded vertices
// offsets (1st vertice top/left position)
OffsetPointer<uint8_t> mpIndices;
OffsetPointer<ImguiVert> mpVertices;
OffsetPointer<ImguiDraw> mpDraws;
inline void ToPointers();
inline void ToOffsets();
};
struct CmdDrawFrame* ConvertToCmdDrawFrame(const ImDrawData* pDearImguiData,
ImGuiMouseCursor cursor);
struct CmdDrawFrame* CompressCmdDrawFrame(const CmdDrawFrame* pDrawFramePrev,
const CmdDrawFrame* pDrawFrameNew);
struct CmdDrawFrame* DecompressCmdDrawFrame(
const CmdDrawFrame* pDrawFramePrev, const CmdDrawFrame* pDrawFramePacked);
} // namespace Internal
} // namespace NetImgui
@@ -0,0 +1,44 @@
// Google modifications:
// - Added #include <cstdint> for uint32_t (not implicitly available in Google)
#pragma once
#include <cstdint>
namespace NetImgui {
namespace Internal {
struct PendingCom;
}
} // namespace NetImgui
namespace NetImgui {
namespace Internal {
namespace Network {
struct SocketInfo;
bool Startup(void);
void Shutdown(void);
SocketInfo* Connect(
const char* ServerHost,
uint32_t ServerPort); // Communication Socket expected to be blocking
SocketInfo* ListenConnect(
SocketInfo* ListenSocket); // Communication Socket expected to be blocking
SocketInfo* ListenStart(
uint32_t ListenPort); // Listening Socket expected to be non blocking
void Disconnect(SocketInfo* pClientSocket);
bool DataReceivePending(
SocketInfo* pClientSocket); // True if some new data if waiting to be
// processed from remote connection
void DataReceive(
SocketInfo* pClientSocket,
PendingCom& PendingComRcv); // Try reading X amount of bytes from remote
// connection, but can fall short.
void DataSend(
SocketInfo* pClientSocket,
PendingCom& PendingComSend); // Try sending X amount of bytes to remote
// connection, but can fall short.
} // namespace Network
} // namespace Internal
} // namespace NetImgui
@@ -0,0 +1,391 @@
// Google modifications:
// - Replaced select() with poll() to avoid FD_SETSIZE (1024) limit and
// a SIGSEGV caused by Linux select() writing to a const timeval placed
// in read-only memory by the compiler.
// - Added logging for network events.
#include "NetImgui_Shared.h"
#include "google/logging.h"
#if defined(_MSC_VER)
#pragma warning(disable : 4221)
#endif
#if NETIMGUI_ENABLED && NETIMGUI_POSIX_SOCKETS_ENABLED
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h> // Required for TCP_NODELAY
#include <poll.h> // Preferred over select() (no FD_SETSIZE limit)
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string> // Required for std::string / std::to_string with GCC
#include <time.h>
#include <unistd.h>
#include <cstdint>
#include "NetImgui_CmdPackets.h"
// NOTE: Removed static_assert(0) as requested changes are implemented below
namespace NetImgui {
namespace Internal {
namespace Network {
//=================================================================================================
// Wrapper around native socket object and init some socket options
//=================================================================================================
struct SocketInfo {
SocketInfo(int socket) : mSocket(socket) {
if (mSocket != -1) {
// Set Non-Blocking
int flags = fcntl(mSocket, F_GETFL, 0);
fcntl(mSocket, F_SETFL, flags | O_NONBLOCK);
// Set TCP No Delay
int flag = 1;
setsockopt(mSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
// Optional: Set Send Buffer Size (Mirroring Win32's attempt)
// int kComsSendBuffer = 2 * mSendSizeMax;
// setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, (char*)&kComsSendBuffer,
// sizeof(kComsSendBuffer));
}
}
int mSocket = -1;
int mSendSizeMax = 1024 * 1024; // Limit tx data to avoid socket error on
// large amount (texture) [cite: 258]
};
bool Startup() {
// No specific startup needed for POSIX sockets like WSAStartup in Winsock
return true;
}
void Shutdown() {
// No specific cleanup needed for POSIX sockets like WSACleanup in Winsock
}
//=================================================================================================
// Try establishing a connection to a remote client at given address
// (Non-Blocking)
//=================================================================================================
SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) {
int ClientSocket = -1;
addrinfo hints, *pResults = nullptr, *pResultCur = nullptr;
SocketInfo* pSocketInfo = nullptr;
char zPortName[32];
sprintf(zPortName, "%u", ServerPort);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(ServerHost, zPortName, &hints, &pResults) != 0) {
return nullptr; // Failed to resolve host
}
for (pResultCur = pResults; pResultCur != nullptr && pSocketInfo == nullptr;
pResultCur = pResultCur->ai_next) {
ClientSocket = socket(pResultCur->ai_family, pResultCur->ai_socktype,
pResultCur->ai_protocol);
if (ClientSocket == -1) {
continue; // Failed to create socket for this address
}
// Set non-blocking *before* connect for non-blocking connect
int flags = fcntl(ClientSocket, F_GETFL, 0);
fcntl(ClientSocket, F_SETFL, flags | O_NONBLOCK);
int Result =
connect(ClientSocket, pResultCur->ai_addr, pResultCur->ai_addrlen);
bool Connected = (Result == 0);
if (Result == -1 && errno == EINPROGRESS) {
// Connection attempt is in progress, use poll to wait
struct pollfd pfd = {ClientSocket, POLLOUT, 0};
Result = poll(&pfd, 1, 1000); // 1 second timeout
if (Result > 0) {
// Select indicated socket is writable, check for connection errors
int optVal;
socklen_t optLen = sizeof(optVal);
if (getsockopt(ClientSocket, SOL_SOCKET, SO_ERROR, &optVal, &optLen) ==
0 &&
optVal == 0) {
Connected = true;
} else {
// Connection failed
Connected = false;
}
} else {
// Select timed out or error
Connected = false;
}
} else if (Result == -1) {
// Immediate connection error
Connected = false;
}
if (Connected) {
pSocketInfo = netImguiNew<SocketInfo>(ClientSocket);
// Socket is already non-blocking from the SocketInfo constructor
} else if (ClientSocket != -1) {
close(ClientSocket); // Close socket if connection failed
ClientSocket = -1;
}
}
freeaddrinfo(pResults);
if (!pSocketInfo && ClientSocket != -1) {
close(ClientSocket); // Clean up socket if loop finished without success
}
return pSocketInfo;
}
//=================================================================================================
// Start waiting for connection request on this socket
//=================================================================================================
SocketInfo* ListenStart(uint32_t ListenPort) {
addrinfo hints, *addrInfo;
int ListenSocket = -1;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // Typically listen on IPv4 for simplicity, or
// AF_UNSPEC for both
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // Use my IP
std::string portStr = std::to_string(ListenPort);
if (getaddrinfo(nullptr, portStr.c_str(), &hints, &addrInfo) != 0) {
return nullptr;
}
ListenSocket =
socket(addrInfo->ai_family, addrInfo->ai_socktype, addrInfo->ai_protocol);
if (ListenSocket != -1) {
#if NETIMGUI_FORCE_TCP_LISTEN_BINDING
int flag = 1;
setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
#ifdef SO_REUSEPORT // SO_REUSEPORT might not be available on all POSIX systems
setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag));
#endif
#endif
if (bind(ListenSocket, addrInfo->ai_addr, addrInfo->ai_addrlen) != -1 &&
listen(ListenSocket, SOMAXCONN) != -1) // Use SOMAXCONN for backlog
{
// Keep listening socket blocking for accept() simplicity,
// the *accepted* socket will be non-blocking via SocketInfo ctor
int flags = fcntl(ListenSocket, F_GETFL, 0);
fcntl(ListenSocket, F_SETFL, flags & (~O_NONBLOCK)); // Ensure blocking
freeaddrinfo(addrInfo);
// Note: We create SocketInfo wrapper here just for consistency,
// but it doesn't set options on the *listening* socket.
return netImguiNew<SocketInfo>(ListenSocket);
}
close(ListenSocket);
}
freeaddrinfo(addrInfo);
return nullptr;
}
//=================================================================================================
// Accept a new connection (blocking call on ListenSocket)
//=================================================================================================
SocketInfo* ListenConnect(SocketInfo* pListenSocket) {
if (pListenSocket && pListenSocket->mSocket != -1) {
sockaddr_storage ClientAddress;
socklen_t Size = sizeof(ClientAddress);
// ListenSocket should be blocking (set in ListenStart)
int ServerSocket =
accept(pListenSocket->mSocket, (sockaddr*)&ClientAddress, &Size);
if (ServerSocket != -1) {
VLOG(1, "TCP connection accepted (fd=%d)", ServerSocket);
// Create SocketInfo wrapper, which sets the new socket to non-blocking
return netImguiNew<SocketInfo>(ServerSocket);
}
}
return nullptr;
}
//=================================================================================================
// Close a connection and free allocated object
//=================================================================================================
void Disconnect(SocketInfo* pClientSocket) {
if (pClientSocket && pClientSocket->mSocket != -1) {
// Set SO_LINGER option to force close and discard pending data
// to ensure the socket is closed immediately and exits the CLOSE_WAIT state
// more reliably
struct linger sl;
sl.l_onoff = 1; // Enable linger
sl.l_linger = 0; // Set timeout to 0 seconds (force RST)
setsockopt(pClientSocket->mSocket, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
shutdown(pClientSocket->mSocket, SHUT_RDWR);
close(pClientSocket->mSocket);
pClientSocket->mSocket = -1; // Mark as closed
}
netImguiDelete(pClientSocket);
}
//=================================================================================================
// Return true if data has been received, or there's a connection error
//=================================================================================================
bool DataReceivePending(SocketInfo* pClientSocket) {
if (!pClientSocket || pClientSocket->mSocket == -1) {
return true; // Error condition
}
// Use poll() instead of select() to avoid FD_SETSIZE limitation.
struct pollfd pfd = {pClientSocket->mSocket, POLLIN, 0};
int result = poll(&pfd, 1, 0); // 0ms timeout = non-blocking check
// poll() syscall error (e.g. EINTR) — treat as "no data" and retry next
// frame rather than speculatively calling recv() on unknown socket state.
if (result < 0) {
return false;
}
// No events on the socket.
if (result == 0) {
return false;
}
// result > 0: the socket has events.
if (pfd.revents & POLLERR) {
// Report as data-pending so the caller's recv() surfaces the error
// through its own error-handling path. Info, not Error: this is routine
// when the peer tears the connection down (e.g. viewer shutdown).
VLOG(1, "DataReceivePending: POLLERR revents=%d", pfd.revents);
return true;
}
if (pfd.revents & POLLHUP) {
// Peer hung up. The persistent TCP proxy keeps connections alive across
// browser refreshes, so a transient POLLHUP does not mean the session is
// over — ignore it.
VLOG(1, "DataReceivePending: POLLHUP revents=%d", pfd.revents);
}
// Only signal data-pending when the kernel has bytes ready to read.
return (pfd.revents & POLLIN) != 0;
}
//=================================================================================================
// Receive as much as possible into PendingCom buffer (Non-Blocking)
//=================================================================================================
void DataReceive(SocketInfo* pClientSocket,
NetImgui::Internal::PendingCom& PendingComRcv) {
// Invalid command or socket state
if (!pClientSocket || pClientSocket->mSocket == -1 ||
!PendingComRcv.pCommand) {
PendingComRcv.bError = true;
return;
}
size_t BytesToRead =
PendingComRcv.pCommand->mSize - PendingComRcv.SizeCurrent;
if (BytesToRead == 0) {
return; // Already fully received
}
// Receive data from remote connection (non-blocking)
ssize_t resultRcv =
recv(pClientSocket->mSocket,
&reinterpret_cast<uint8_t*>(
PendingComRcv.pCommand)[PendingComRcv.SizeCurrent],
BytesToRead,
0); // No flags, non-blocking behavior comes from socket setting
if (resultRcv > 0) {
// Successfully received some data
PendingComRcv.SizeCurrent += static_cast<size_t>(resultRcv);
PendingComRcv.bError = false; // Reset error flag on successful read
} else if (resultRcv == 0) {
// Connection closed gracefully by peer
PendingComRcv.bError = true;
VLOG(1, "DataReceive: Connection closed gracefully");
} else { // resultRcv < 0
// Error occurred
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// Not an error, just no data available right now on non-blocking socket
PendingComRcv.bError = false;
} else {
// Actual socket error
PendingComRcv.bError = true;
}
}
}
//=================================================================================================
// Send as much as possible from PendingCom buffer (Non-Blocking)
//=================================================================================================
void DataSend(SocketInfo* pClientSocket,
NetImgui::Internal::PendingCom& PendingComSend) {
// Invalid command or socket state
if (!pClientSocket || pClientSocket->mSocket == -1 ||
!PendingComSend.pCommand) {
PendingComSend.bError = true;
return;
}
size_t BytesRemaining =
PendingComSend.pCommand->mSize - PendingComSend.SizeCurrent;
if (BytesRemaining == 0) {
return; // Already fully sent
}
// Limit send size per call [cite: 281]
size_t BytesToSend =
BytesRemaining > static_cast<size_t>(pClientSocket->mSendSizeMax)
? static_cast<size_t>(pClientSocket->mSendSizeMax)
: BytesRemaining;
// Send data to remote connection (non-blocking)
ssize_t resultSent =
send(pClientSocket->mSocket,
&reinterpret_cast<const uint8_t*>(
PendingComSend.pCommand)[PendingComSend.SizeCurrent],
BytesToSend,
MSG_NOSIGNAL); // Use MSG_NOSIGNAL to prevent SIGPIPE on Linux if
// connection is broken
if (resultSent > 0) {
// Successfully sent some data
PendingComSend.SizeCurrent += static_cast<size_t>(resultSent);
PendingComSend.bError = false; // Reset error flag on successful send
} else if (resultSent == 0) {
// This shouldn't typically happen with TCP unless BytesToSend was 0
PendingComSend.bError = false; // Treat as non-error for now
} else { // resultSent < 0
// Error occurred
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// Not an error, socket buffer is full, try again later
PendingComSend.bError = false;
} else {
// Actual socket error (e.g., EPIPE if connection broken and MSG_NOSIGNAL
// not used/supported)
PendingComSend.bError = true;
}
}
}
} // namespace Network
} // namespace Internal
} // namespace NetImgui
#else
// Prevents Linker warning LNK4221 in Visual Studio (This object file does not
// define any previously undefined public symbols, so it will not be used by any
// link operation that consumes this library)
extern int sSuppresstLNK4221_NetImgui_NetworkPosix;
int sSuppresstLNK4221_NetImgui_NetworkPosix(0);
#endif // #if NETIMGUI_ENABLED && NETIMGUI_POSIX_SOCKETS_ENABLED
@@ -0,0 +1,258 @@
#include "NetImgui_Shared.h"
#if NETIMGUI_ENABLED && NETIMGUI_WINSOCKET_ENABLED
#include <WS2tcpip.h>
#include <WinSock2.h>
#include "NetImgui_WarningDisableStd.h"
#if defined(_MSC_VER)
#pragma comment(lib, "ws2_32")
#endif
#include "NetImgui_CmdPackets.h"
namespace NetImgui {
namespace Internal {
namespace Network {
//=================================================================================================
// Wrapper around native socket object and init some socket options
//=================================================================================================
struct SocketInfo {
SocketInfo(SOCKET socket) : mSocket(socket) {
u_long kNonBlocking = true;
ioctlsocket(mSocket, static_cast<long>(FIONBIO), &kNonBlocking);
constexpr DWORD kComsNoDelay = 1;
setsockopt(mSocket, SOL_SOCKET, TCP_NODELAY,
reinterpret_cast<const char*>(&kComsNoDelay),
sizeof(kComsNoDelay));
const int kComsSendBuffer = 2 * mSendSizeMax;
setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char*>(&kComsSendBuffer),
sizeof(kComsSendBuffer));
// constexpr int kComsRcvBuffer = 1014*1024;
// setsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const
// char*>(&kComsRcvBuffer), sizeof(kComsRcvBuffer));
}
SOCKET mSocket;
int mSendSizeMax =
1024 *
1024; // Limit tx data to avoid socket error on large amount (texture)
};
bool Startup() {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
return true;
}
void Shutdown() { WSACleanup(); }
//=================================================================================================
// Try establishing a connection to a remote client at given address
//=================================================================================================
SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) {
const timeval kConnectTimeout = {
1, 0}; // Waiting 1 seconds before failing connection attempt
u_long kNonBlocking = true;
SOCKET ClientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (ClientSocket == INVALID_SOCKET) return nullptr;
char zPortName[32] = {};
addrinfo* pResults = nullptr;
SocketInfo* pSocketInfo = nullptr;
NetImgui::Internal::StringFormat(zPortName, "%i", ServerPort);
getaddrinfo(ServerHost, zPortName, nullptr, &pResults);
addrinfo* pResultCur = pResults;
fd_set SocketSet;
ioctlsocket(ClientSocket, static_cast<long>(FIONBIO), &kNonBlocking);
while (pResultCur && !pSocketInfo) {
int Result = connect(ClientSocket, pResultCur->ai_addr,
static_cast<int>(pResultCur->ai_addrlen));
bool Connected = Result != SOCKET_ERROR;
// Not connected yet, wait some time before bailing out
if (Result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) {
FD_ZERO(&SocketSet);
FD_SET(ClientSocket, &SocketSet);
Result = select(0, nullptr, &SocketSet, nullptr, &kConnectTimeout);
Connected =
Result == 1; // when 1 socket ready for write, otherwise it's -1 or 0
}
if (Connected) {
pSocketInfo = netImguiNew<SocketInfo>(ClientSocket);
}
pResultCur = pResultCur->ai_next;
}
freeaddrinfo(pResults);
if (!pSocketInfo) {
closesocket(ClientSocket);
}
return pSocketInfo;
}
//=================================================================================================
// Start waiting for connection request on this socket
//=================================================================================================
SocketInfo* ListenStart(uint32_t ListenPort) {
SOCKET ListenSocket = INVALID_SOCKET;
if ((ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) !=
INVALID_SOCKET) {
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(static_cast<USHORT>(ListenPort));
#if NETIMGUI_FORCE_TCP_LISTEN_BINDING
constexpr BOOL ReUseAdrValue(true);
setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&ReUseAdrValue),
sizeof(ReUseAdrValue));
#endif
if (bind(ListenSocket, reinterpret_cast<sockaddr*>(&server),
sizeof(server)) != SOCKET_ERROR &&
listen(ListenSocket, 0) != SOCKET_ERROR) {
u_long kIsNonBlocking = false;
ioctlsocket(ListenSocket, static_cast<long>(FIONBIO), &kIsNonBlocking);
return netImguiNew<SocketInfo>(ListenSocket);
}
closesocket(ListenSocket);
}
return nullptr;
}
//=================================================================================================
// Establish a new connection to a remote request
//=================================================================================================
SocketInfo* ListenConnect(SocketInfo* ListenSocket) {
if (ListenSocket) {
sockaddr ClientAddress;
int Size(sizeof(ClientAddress));
SOCKET ClientSocket = accept(ListenSocket->mSocket, &ClientAddress, &Size);
if (ClientSocket != INVALID_SOCKET) {
return netImguiNew<SocketInfo>(ClientSocket);
}
}
return nullptr;
}
//=================================================================================================
// Close a connection and free allocated object
//=================================================================================================
void Disconnect(SocketInfo* pClientSocket) {
if (pClientSocket) {
shutdown(pClientSocket->mSocket, SD_BOTH);
closesocket(pClientSocket->mSocket);
netImguiDelete(pClientSocket);
}
}
//=================================================================================================
// Return true if data has been received, or there's a connection error
//=================================================================================================
bool DataReceivePending(SocketInfo* pClientSocket) {
const timeval kConnectTimeout = {0, 0}; // No wait time
if (pClientSocket) {
fd_set fdSetRead;
fd_set fdSetErr;
FD_ZERO(&fdSetRead);
FD_ZERO(&fdSetErr);
FD_SET(pClientSocket->mSocket, &fdSetRead);
FD_SET(pClientSocket->mSocket, &fdSetErr);
// Note: return true if data ready or connection error (to exit parent loop
// waiting on data)
int result = select(0, &fdSetRead, nullptr, &fdSetErr, &kConnectTimeout);
return result != 0;
}
return true;
}
//=================================================================================================
// Receive as much as possible a command and keep track of transfer status
//=================================================================================================
void DataReceive(SocketInfo* pClientSocket,
NetImgui::Internal::PendingCom& PendingComRcv) {
// Invalid command
if (!pClientSocket || !PendingComRcv.pCommand || !pClientSocket->mSocket) {
PendingComRcv.bError = true;
return;
}
// Receive data from remote connection
int resultRcv = recv(pClientSocket->mSocket,
&reinterpret_cast<char*>(
PendingComRcv.pCommand)[PendingComRcv.SizeCurrent],
static_cast<int>(PendingComRcv.pCommand->mSize -
PendingComRcv.SizeCurrent),
0);
// Note: 'DataReceive' is called after pending data has been confirm.
// 0 received data means connection lost
if (resultRcv != SOCKET_ERROR) {
PendingComRcv.SizeCurrent += static_cast<size_t>(resultRcv);
PendingComRcv.bError |=
resultRcv <= 0; // Error if no data read since DataReceivePending()
// said there was some available
}
// Connection error, abort transmission
else if (WSAGetLastError() != WSAEWOULDBLOCK) {
PendingComRcv.bError = true;
}
}
//=================================================================================================
// Receive as much as possible a command and keep track of transfer status
//=================================================================================================
void DataSend(SocketInfo* pClientSocket,
NetImgui::Internal::PendingCom& PendingComSend) {
// Invalid command
if (!pClientSocket || !PendingComSend.pCommand || !pClientSocket->mSocket) {
PendingComSend.bError = true;
return;
}
// Send data to remote connection
int sizeToSend = static_cast<int>(PendingComSend.pCommand->mSize -
PendingComSend.SizeCurrent);
sizeToSend = sizeToSend > pClientSocket->mSendSizeMax
? pClientSocket->mSendSizeMax
: sizeToSend;
int resultSent =
send(pClientSocket->mSocket,
&reinterpret_cast<char*>(
PendingComSend.pCommand)[PendingComSend.SizeCurrent],
sizeToSend, 0);
if (resultSent != SOCKET_ERROR) {
PendingComSend.SizeCurrent += static_cast<size_t>(resultSent);
}
// Connection error, abort transmission
else if (WSAGetLastError() != WSAEWOULDBLOCK) {
PendingComSend.bError = true;
}
}
} // namespace Network
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_WarningReenable.h"
#else
// Prevents Linker warning LNK4221 in Visual Studio (This object file does not
// define any previously undefined public symbols, so it will not be used by any
// link operation that consumes this library)
extern int sSuppresstLNK4221_NetImgui_NetworkWin23;
int sSuppresstLNK4221_NetImgui_NetworkWin23(0);
#endif // #if NETIMGUI_ENABLED && NETIMGUI_WINSOCKET_ENABLED
@@ -0,0 +1,205 @@
#pragma once
//=================================================================================================
// Include NetImgui_Api.h with almost no warning suppression.
// this is to make sure library user does not need to suppress any
#if defined(_MSC_VER)
#pragma warning(disable \
: 4464) // warning C4464: relative include path contains '..'
#endif
#ifndef NETIMGUI_INTERNAL_INCLUDE
#define NETIMGUI_INTERNAL_INCLUDE 1
#include "NetImgui_Api.h"
#undef NETIMGUI_INTERNAL_INCLUDE
#else
#include "NetImgui_Api.h"
#endif
#if NETIMGUI_ENABLED
//=================================================================================================
// Include a few standard c++ header, with additional warning suppression
#include <atomic>
#include <chrono>
#include <thread>
#include <vector>
#include "NetImgui_WarningDisableStd.h"
#include "NetImgui_WarningReenable.h"
//=================================================================================================
//=================================================================================================
#include "NetImgui_WarningDisable.h"
namespace NetImgui {
namespace Internal {
using ComDataType = uint64_t;
constexpr size_t ComDataSize = sizeof(ComDataType);
using ClientTextureID = uint64_t;
//=============================================================================
// All allocations made by netImgui goes through here.
// Relies in ImGui allocator
//=============================================================================
template <typename TType, typename... Args>
TType* netImguiNew(Args... args);
template <typename TType>
TType* netImguiSizedNew(size_t placementSize);
template <typename TType>
void netImguiDelete(TType* pData);
template <typename TType>
void netImguiDeleteSafe(TType*& pData);
class ScopedImguiContext {
public:
ScopedImguiContext(ImGuiContext* pNewContext)
: mpSavedContext(ImGui::GetCurrentContext()) {
ImGui::SetCurrentContext(pNewContext);
}
~ScopedImguiContext() { ImGui::SetCurrentContext(mpSavedContext); }
protected:
ImGuiContext* mpSavedContext;
};
template <typename TType>
class ScopedValue {
public:
ScopedValue(TType& ValueRef, TType Value)
: mValueRef(ValueRef), mValueRestore(ValueRef) {
mValueRef = Value;
}
~ScopedValue() { mValueRef = mValueRestore; }
protected:
TType& mValueRef;
TType mValueRestore;
uint8_t mPadding[sizeof(void*) - (sizeof(TType) % 8)] = {};
// Prevents warning about implicitly delete functions
ScopedValue(const ScopedValue&) = delete;
ScopedValue(const ScopedValue&&) = delete;
void operator=(const ScopedValue&) = delete;
};
using ScopedBool = ScopedValue<bool>;
//=============================================================================
// Class to safely exchange a pointer between two threads
//=============================================================================
template <typename TType>
class ExchangePtr {
public:
ExchangePtr() : mpData(nullptr) {}
~ExchangePtr();
inline TType* Release();
inline void Assign(TType*& pNewData);
inline void Free();
inline bool IsNull() const { return mpData.load() == nullptr; }
private:
std::atomic<TType*> mpData;
// Prevents warning about implicitly delete functions
private:
ExchangePtr(const ExchangePtr&) = delete;
ExchangePtr(const ExchangePtr&&) = delete;
void operator=(const ExchangePtr&) = delete;
};
//=============================================================================
// Make data serialization easier
//=============================================================================
template <typename TType>
struct OffsetPointer {
inline OffsetPointer();
inline explicit OffsetPointer(TType* pPointer);
inline explicit OffsetPointer(uint64_t offset);
inline bool IsPointer() const;
inline bool IsOffset() const;
inline TType* ToPointer();
inline uint64_t ToOffset();
inline TType* operator->();
inline const TType* operator->() const;
inline TType& operator[](size_t index);
inline const TType& operator[](size_t index) const;
inline TType* Get();
inline const TType* Get() const;
inline const ComDataType* GetComData() const;
inline uint64_t GetOff() const;
inline void SetPtr(TType* pPointer);
inline void SetComDataPtr(ComDataType* pPointer);
inline void SetOff(uint64_t offset);
private:
union {
uint64_t mOffset;
TType* mPointer;
};
};
//=============================================================================
//=============================================================================
template <typename TType, size_t TCount>
class Ringbuffer {
public:
Ringbuffer() : mPosCur(0), mPosLast(0) {}
void AddData(const TType* pData, size_t& count);
bool ReadData(TType* pData);
private:
TType mBuffer[TCount] = {0};
std::atomic_uint64_t mPosCur;
std::atomic_uint64_t mPosLast;
// Prevents warning about implicitly delete functions
private:
Ringbuffer(const Ringbuffer&) = delete;
Ringbuffer(const Ringbuffer&&) = delete;
void operator=(const Ringbuffer&) = delete;
};
template <typename T, std::size_t N>
constexpr std::size_t ArrayCount(T const (&)[N]) noexcept {
return N;
}
//=============================================================================
//=============================================================================
template <size_t charCount>
inline void StringCopy(char (&output)[charCount], const char* pSrc,
size_t srcCharCount = 0xFFFFFFFE);
template <size_t charCount>
int StringFormat(char (&output)[charCount], char const* const format, ...);
//=============================================================================
// Get the (value / Denominator) rounded up to the next int value big enough
//=============================================================================
template <typename IntType>
IntType DivUp(IntType Value, IntType Denominator);
//=============================================================================
// Get the rounded up value
//=============================================================================
template <typename IntType>
IntType RoundUp(IntType Value, IntType Round);
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
inline NetImgui::eTexFormat ConvertTextureFormat(ImTextureFormat ImFormat);
inline ClientTextureID ConvertToClientTexID(const ImTextureRef& textureRef);
#endif
inline ClientTextureID ConvertToClientTexID(ImTextureID textureID);
inline ImTextureID ConvertFromClientTexID(ClientTextureID textureID);
} // namespace Internal
} // namespace NetImgui
#include "NetImgui_Shared.inl"
#include "NetImgui_WarningReenable.h"
#endif // NETIMGUI_ENABLED
@@ -0,0 +1,341 @@
#pragma once
#include <assert.h>
#include <string.h>
namespace NetImgui { namespace Internal
{
template <typename TType, typename... Args>
TType* netImguiNew(Args... args)
{
return new( ImGui::MemAlloc(sizeof(TType)) ) TType(args...);
}
template <typename TType>
TType* netImguiSizedNew(size_t placementSize)
{
return new( ImGui::MemAlloc(placementSize > sizeof(TType) ? placementSize : sizeof(TType)) ) TType();
}
template <typename TType>
void netImguiDelete(TType* pData)
{
if( pData )
{
pData->~TType();
ImGui::MemFree(pData);
}
}
template <typename TType>
void netImguiDeleteSafe(TType*& pData)
{
netImguiDelete(pData);
pData = nullptr;
}
//=============================================================================
// Acquire ownership of the resource
//=============================================================================
template <typename TType>
TType* ExchangePtr<TType>::Release()
{
return mpData.exchange(nullptr);
}
//-----------------------------------------------------------------------------
// Take ownership of the provided data.
// If there's a previous unclaimed pointer to some data, release it
//-----------------------------------------------------------------------------
template <typename TType>
void ExchangePtr<TType>::Assign(TType*& pNewData)
{
netImguiDelete( mpData.exchange(pNewData) );
pNewData = nullptr;
}
template <typename TType>
void ExchangePtr<TType>::Free()
{
TType* pNull(nullptr);
Assign(pNull);
}
template <typename TType>
ExchangePtr<TType>::~ExchangePtr()
{
Free();
}
//=============================================================================
//
//=============================================================================
template <typename TType>
OffsetPointer<TType>::OffsetPointer()
: mOffset(0)
{
SetOff(0);
}
template <typename TType>
OffsetPointer<TType>::OffsetPointer(TType* pPointer)
{
SetPtr(pPointer);
}
template <typename TType>
OffsetPointer<TType>::OffsetPointer(uint64_t offset)
{
SetOff(offset);
}
template <typename TType>
void OffsetPointer<TType>::SetPtr(TType* pPointer)
{
mOffset = 0; // Remove 'offset flag' that can be left active on non 64bits pointer
mPointer = pPointer;
}
template <typename TType>
void OffsetPointer<TType>::SetComDataPtr(ComDataType* pPointer)
{
SetPtr(reinterpret_cast<TType*>(pPointer));
}
template <typename TType>
void OffsetPointer<TType>::SetOff(uint64_t offset)
{
mOffset = offset | 0x0000000000000001u;
}
template <typename TType>
uint64_t OffsetPointer<TType>::GetOff()const
{
return mOffset & ~0x0000000000000001u;
}
template <typename TType>
bool OffsetPointer<TType>::IsOffset()const
{
return (mOffset & 0x0000000000000001u) != 0;
}
template <typename TType>
bool OffsetPointer<TType>::IsPointer()const
{
return !IsOffset();
}
template <typename TType>
TType* OffsetPointer<TType>::ToPointer()
{
assert(IsOffset());
SetPtr( reinterpret_cast<TType*>( reinterpret_cast<uint64_t>(&mPointer) + GetOff() ) );
return mPointer;
}
template <typename TType>
uint64_t OffsetPointer<TType>::ToOffset()
{
assert(IsPointer());
SetOff( reinterpret_cast<uint64_t>(mPointer) - reinterpret_cast<uint64_t>(&mPointer) );
return mOffset;
}
template <typename TType>
TType* OffsetPointer<TType>::operator->()
{
assert(IsPointer());
return mPointer;
}
template <typename TType>
const TType* OffsetPointer<TType>::operator->()const
{
assert(IsPointer());
return mPointer;
}
template <typename TType>
TType* OffsetPointer<TType>::Get()
{
assert(IsPointer());
return mPointer;
}
template <typename TType>
const TType* OffsetPointer<TType>::Get()const
{
assert(IsPointer());
return mPointer;
}
template <typename TType>
const ComDataType* OffsetPointer<TType>::GetComData()const
{
return reinterpret_cast<const ComDataType*>(Get());
}
template <typename TType>
TType& OffsetPointer<TType>::operator[](size_t index)
{
assert(IsPointer());
return mPointer[index];
}
template <typename TType>
const TType& OffsetPointer<TType>::operator[](size_t index)const
{
assert(IsPointer());
return mPointer[index];
}
//=============================================================================
template <typename TType, size_t TCount>
void Ringbuffer<TType,TCount>::AddData(const TType* pData, size_t& count)
//=============================================================================
{
size_t i(0);
while (i < count && (mPosLast - mPosCur < TCount)) {
mBuffer[mPosLast % TCount] = pData[i];
mPosLast++;
i++;
}
count = i;
}
//=============================================================================
template <typename TType, size_t TCount>
bool Ringbuffer<TType,TCount>::ReadData(TType* pData)
//=============================================================================
{
if (mPosCur < mPosLast)
{
*pData = mBuffer[mPosCur % TCount];
mPosCur++;
return true;
}
return false;
}
//=============================================================================
// The _s string functions are a mess. There's really no way to do this right
// in a cross-platform way. Best solution I've found is to set just use
// strncpy, infer the buffer length, and null terminate. Still need to suppress
// the warning on Windows.
// See https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/
// and many other discussions online on the topic.
//=============================================================================
template <size_t charCount>
void StringCopy(char (&output)[charCount], const char* pSrc, size_t srcCharCount)
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'strncpy': This function or variable may be unsafe.
#endif
size_t charToCopyCount = charCount < srcCharCount + 1 ? charCount : srcCharCount + 1;
strncpy(output, pSrc, charToCopyCount - 1);
output[charCount - 1] = 0;
#if defined(_MSC_VER) && defined(__clang__)
#pragma clang diagnostic pop
#elif defined(_MSC_VER)
#pragma warning (pop)
#endif
}
template <size_t charCount>
int StringFormat(char(&output)[charCount], char const* const format, ...)
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
#endif
va_list args;
va_start(args, format);
int w = vsnprintf(output, charCount, format, args);
va_end(args);
output[charCount - 1] = 0;
return w;
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
}
//=============================================================================
//=============================================================================
template <typename IntType>
IntType DivUp(IntType Value, IntType Denominator)
{
return (Value + Denominator - 1) / Denominator;
}
template <typename IntType>
IntType RoundUp(IntType Value, IntType Round)
{
return DivUp(Value, Round) * Round;
}
union TextureCastHelperUnion
{
const void* TexData;
ImTextureID TexID;
ClientTextureID TexClientID;
};
#if NETIMGUI_IMGUI_TEXTURES_ENABLED
NetImgui::eTexFormat ConvertTextureFormat(ImTextureFormat ImFormat)
{
switch(ImFormat)
{
case ImTextureFormat_RGBA32: return eTexFormat::kTexFmtRGBA8;
case ImTextureFormat_Alpha8: return eTexFormat::kTexFmtA8;
}
return eTexFormat::kTexFmtRGBA8;
}
ClientTextureID ConvertToClientTexID(const ImTextureRef& textureRef)
{
static_assert(sizeof(uint64_t) >= sizeof(ImTextureID), "ImTextureID is bigger than 64bits, CmdTexture::mTextureId needs to be updated to support it");
TextureCastHelperUnion textureUnion;
textureUnion.TexClientID = 0;
if( textureRef._TexData ){
//Note: Cannot rely on textureRef.GetTexID() because it uses a pointer to
// a texture object that might not have been created by the backend yet.
// Instead, use a stable value that remain valid for texture lifetime,
// to id it with the NetImgui Server
textureUnion.TexClientID = static_cast<ClientTextureID>(textureRef._TexData->UniqueID);
}
else{
textureUnion.TexID = textureRef._TexID;
}
return textureUnion.TexClientID;
}
#endif
ClientTextureID ConvertToClientTexID(ImTextureID textureID)
{
static_assert(sizeof(uint64_t) >= sizeof(ImTextureID), "ImTextureID is bigger than 64bits, CmdTexture::mTextureId needs to be updated to support it");
TextureCastHelperUnion textureUnion;
textureUnion.TexClientID = 0;
textureUnion.TexID = textureID;
return textureUnion.TexClientID;
}
ImTextureID ConvertFromClientTexID(ClientTextureID clientTexID)
{
TextureCastHelperUnion textureUnion;
textureUnion.TexClientID = clientTexID;
return textureUnion.TexID;
}
}} //namespace NetImgui::Internal
@@ -0,0 +1,52 @@
#pragma once
//
// Deactivate a few warnings to allow internal netImgui code to compile
// with 'Warning as error' and '-Wall' compile actions enabled
//
//=================================================================================================
// Clang
//=================================================================================================
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#pragma clang diagnostic ignored "-Wold-style-cast" // For ImTextureID_Invalid
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#pragma clang diagnostic ignored "-Wswitch-default"
#pragma clang diagnostic ignored \
"-Wnontrivial-memcall" // For ImGui::IO memcpy warning
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning(disable : 5032) // detected #pragma warning(push) with no
// corresponding #pragma warning(pop)
#pragma warning(push)
#pragma warning(disable : 4365) // conversion from 'long' to 'unsigned int',
// signed/unsigned mismatch for <atomic>
#pragma warning(disable : 4464) // relative include path contains '..'
#pragma warning(disable \
: 4514) // unreferenced inline function has been removed
#pragma warning( \
disable \
: 4577) // 'noexcept' used with no exception handling mode specified;
// termination on exception is not guaranteed. Specify
#pragma warning(disable : 4710) // 'xxx': function not inlined
#pragma warning( \
disable : 4711) // function 'xxx' selected for automatic inline expansion
#pragma warning( \
disable \
: 4826) // Conversion from 'TType *' to 'uint64_t' is sign-extended. This
// may cause unexpected runtime behavior.
#pragma warning(disable \
: 5031) // #pragma warning(pop): likely mismatch, popping
// warning state pushed in different file
#pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for
// memory load if / Qspectre switch specified
#pragma warning(disable : 5264) // 'xxx': 'const' variable is not used
#endif
@@ -0,0 +1,48 @@
#pragma once
//
// Deactivate a few warnings to allow Imgui header includes,
// without generating warnings in '-Wall' compile actions enabled
//
//=================================================================================================
// Clang
//=================================================================================================
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
#pragma clang diagnostic ignored \
"-Wnonportable-include-path" // Sharpmake convert include path to
// lowercase, avoid warning
#pragma clang diagnostic ignored \
"-Wreserved-identifier" // Enum values using '__' or member starting with
// '_' in imgui.h
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning(push)
#pragma warning( \
disable : 4514) // 'xxx': unreferenced inline function has been removed
#pragma warning(disable : 4365) // '=': conversion from 'ImGuiTabItemFlags' to
// 'ImGuiID', signed/unsigned mismatch
#pragma warning(disable : 4710) // 'xxx': function not inlined
#pragma warning( \
disable \
: 4820) // 'xxx': 'yyy' bytes padding added after data member 'zzz'
#pragma warning(disable \
: 5031) // #pragma warning(pop): likely mismatch, popping
// warning state pushed in different file
#pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for
// memory load if /Qspectre switch specified
#if _MSC_VER >= 1920
#pragma warning(disable : 5219) // implicit conversion from 'int' to 'float',
// possible loss of data
#endif
#pragma warning( \
disable \
: 26495) // Code Analysis warning : Variable
// 'ImGuiStorage::ImGuiStoragePair::<unnamed-tag>::val_p' is
// uninitialized. Always initialize a member variable (type.6).
#endif
@@ -0,0 +1,32 @@
#pragma once
//
// Deactivate a few more warnings to allow standard header includes,
// without generating warnings in '-Wall' compile actions enabled
//
#include "NetImgui_WarningDisable.h"
//=================================================================================================
// Clang
//=================================================================================================
#if defined(__clang__)
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning(disable \
: 4061) // enumerator xxx in switch of enum yyy is not
// explicitly handled by a case label (d3d11.h)
#pragma warning(disable \
: 4548) // expression before comma has no effect; expected
// expression with side - effect (malloc.h VS2017)
#pragma warning(disable \
: 4668) // xxx is not defined as a preprocessor macro,
// replacing with '0' for '#if/#elif' (winsock2.h)
#pragma warning(disable : 4574) // xxx is defined to be '0': did you mean to
// use yyy (winsock2.h VS2017)
#pragma warning( \
disable : 4820) // xxx : yyy bytes padding added after data member zzz
#endif
@@ -0,0 +1,15 @@
#pragma once
//=================================================================================================
// Clang
//=================================================================================================
#if defined(__clang__)
#pragma clang diagnostic pop
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif
@@ -0,0 +1,195 @@
//=================================================================================================
// SAMPLE
//-------------------------------------------------------------------------------------------------
// Common code shared by all samples
//=================================================================================================
#include "Sample.h"
#include <NetImgui_Api.h>
#include <math.h>
#include "../../ServerApp/Source/Fonts/Roboto_Medium.cpp"
namespace Sample {
//=================================================================================================
// Constructor
//-------------------------------------------------------------------------------------------------
//
//=================================================================================================
Base::Base(const char* sampleName) : mSampleName(sampleName) {
#if NETIMGUI_ENABLED
mConnect_PortClient = NetImgui::kDefaultClientPort;
mConnect_PortServer = NetImgui::kDefaultServerPort;
#endif
}
//=================================================================================================
// Startup
//-------------------------------------------------------------------------------------------------
//
//=================================================================================================
bool Base::Startup() {
#if NETIMGUI_ENABLED
if (!NetImgui::Startup()) return false;
#endif
AddFont();
return true;
}
//=================================================================================================
// Shutdown
//-------------------------------------------------------------------------------------------------
//
//=================================================================================================
void Base::Shutdown() {
#if NETIMGUI_ENABLED
NetImgui::Shutdown();
#endif
}
//=================================================================================================
// AddFont
//-------------------------------------------------------------------------------------------------
// Add and configure the fonts wanted in the demo.
// Method can be overriden to use different font, byt default to Roboto 18pts
//
// Note: Since Dear ImGui 1.92+, we do not need to manage font
// scaling/dpi at all,
// It is automatically done, and NetImgui Server also
//assign desired DPI behind the scene.
//=================================================================================================
void Base::AddFont() {
constexpr float kFontPixelSize = 16.f;
ImFontConfig FontConfig = {};
// Using Roboto Font for prettier results
#if 1
// Note: Using memcpy to avoid warnings related to OS string copy variations,
// and cannot rely on 'NetImgui::Internal::StringCopy' that can
//handle this problem because 'SampleDisabled' couln't compile properly
const char FontName[] = "Roboto Medium";
memcpy(FontConfig.Name, FontName, sizeof(FontName));
ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(
Roboto_Medium_compressed_data, Roboto_Medium_compressed_size,
kFontPixelSize, &FontConfig);
// But can as easily rely on the default font
#else
FontConfig.SizePixels = kFontPixelSize;
FontAtlas->AddFontDefault(&FontConfig);
#endif
}
//=================================================================================================
// Draw_Connect
//-------------------------------------------------------------------------------------------------
// Function called by all samples, to display the Connection Options, and some
// other default MainMenu entries.
//=================================================================================================
void Base::Draw_Connect() {
#if NETIMGUI_ENABLED
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 6));
if (ImGui::BeginMainMenuBar()) {
ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(0.1, 1, 0.1, 1), "%s", mSampleName);
ImGui::SameLine(0, 32);
//-----------------------------------------------------------------------------------------
if (NetImgui::IsConnected())
//-----------------------------------------------------------------------------------------
{
ImGui::TextUnformatted("Status: Connected");
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 3));
ImGui::SetCursorPosY(3);
if (ImGui::Button(" Disconnect ")) {
NetImgui::Disconnect();
}
ImGui::PopStyleVar();
}
//-----------------------------------------------------------------------------------------
else if (NetImgui::IsConnectionPending())
//-----------------------------------------------------------------------------------------
{
ImGui::TextUnformatted("Status: Waiting Server");
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 3));
ImGui::SetCursorPosY(3);
if (ImGui::Button(" Cancel ")) {
NetImgui::Disconnect();
}
ImGui::PopStyleVar();
}
//-----------------------------------------------------------------------------------------
else // No connection
//-----------------------------------------------------------------------------------------
{
//-------------------------------------------------------------------------------------
if (ImGui::BeginMenu("[ Connect To ]"))
//-------------------------------------------------------------------------------------
{
ImGui::TextColored(ImVec4(0.1, 1, 0.1, 1), "Server Settings");
ImGui::InputText("Hostname", mConnect_HostnameServer,
sizeof(mConnect_HostnameServer));
if (ImGui::IsItemHovered())
ImGui::SetTooltip(
"Address of PC running the netImgui server application. Can be "
"an IP like 127.0.0.1");
ImGui::InputInt("Port", &mConnect_PortServer);
ImGui::NewLine();
ImGui::Separator();
if (ImGui::Button("Connect",
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
NetImgui::ConnectToApp(mSampleName, mConnect_HostnameServer,
mConnect_PortServer, mCallback_ThreadLaunch);
}
ImGui::EndMenu();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip(
"Attempt a connection to a remote netImgui server at the provided "
"address.");
//-------------------------------------------------------------------------------------
if (ImGui::BeginMenu("[ Wait For ]"))
//-------------------------------------------------------------------------------------
{
ImGui::TextColored(ImVec4(0.1, 1, 0.1, 1), "Client Settings");
ImGui::InputInt("Port", &mConnect_PortClient);
ImGui::NewLine();
ImGui::Separator();
if (ImGui::Button("Listen",
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
NetImgui::ConnectFromApp(mSampleName, mConnect_PortClient,
mCallback_ThreadLaunch);
}
ImGui::EndMenu();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip(
"Start listening for a connection request by a remote netImgui "
"server, on the provided Port.");
}
ImGui::SameLine(0, 40);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.8, 0.8, 0.8, 0.9));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 3));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize,
mbShowDemoWindow ? 1.f : 0.f);
ImGui::SetCursorPosY(3);
if (ImGui::Button(" Show ImGui Demo ")) {
mbShowDemoWindow = !mbShowDemoWindow;
}
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImGui::EndMainMenuBar();
}
ImGui::PopStyleVar();
#endif // #if NETIMGUI_ENABLED
if (mbShowDemoWindow) {
ImGui::ShowDemoWindow(&mbShowDemoWindow);
}
}
}; // namespace Sample
@@ -0,0 +1,55 @@
#pragma once
#include <NetImgui_Api.h>
// Reusing internal NetImgui functions for TextureID conversion and String copy.
// Normally, you wouldn't include this file
#include <Private/NetImgui_Shared.h>
// Forward declares when NetImgui is not enabled
// When NetImgui is disabled, it doesn't include these needed headers
#if !NETIMGUI_ENABLED
#include "imgui.h"
#endif
namespace Sample {
class Base {
public:
Base(const char* sampleName); //!< Constructor receiving pointer to constant
//!< string that must remains valid
virtual bool Startup(); //!< Called once when starting
virtual void Shutdown(); //!< Called once when exiting
virtual void Draw() = 0; //!< Each sample should have their Dear ImGui
//!< drawing routines in this overloaded method
virtual void AddFont(); //!< Called on startup to add the wanted font
protected:
void Draw_Connect(); //!< Display UI for initiating a connection to the
//!< remote NetImgui server application
const char* mSampleName =
nullptr; //!< Name displayed in the Main Menu bar (must receive string
//!< pointer in constructor that remains valid)
bool mbShowDemoWindow =
!NETIMGUI_ENABLED; //!< If we should show the Dear ImGui demo window
#if NETIMGUI_ENABLED
NetImgui::ThreadFunctPtr mCallback_ThreadLaunch =
nullptr; //!< [Optional] Thread launcher callback assigned on NetImgui
//!< connection. Used to start a new thread for coms with
//!< NetImgui server
char mConnect_HostnameServer[128] = {
"localhost"}; //!< IP/Hostname used to send a connection request when
//!< when trying to reach the server
int mConnect_PortServer = 0; //!< Port used to send a connection request when
//!< when trying to reach the server
int mConnect_PortClient =
0; //!< Port opened when waiting for a server connection request
#endif
};
}; // namespace Sample
Sample::Base& GetSample(); // Each Sample must implement this function and
// return a valid sample object
#include <Client/Private/NetImgui_WarningDisable.h>
@@ -0,0 +1,423 @@
// Dear ImGui: standalone example application for DirectX 11
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/
// folder).
// - Introduction, links and more at the top of imgui.cpp
//=================================================================================================
// @SAMPLE_EDIT
#include <chrono>
#include <thread>
#include "Sample.h"
//=================================================================================================
#include <d3d11.h>
#include <tchar.h>
#include "imgui.h"
#include "imgui_impl_dx11.h"
#include "imgui_impl_win32.h"
// Data
ID3D11Device* g_pd3dDevice = nullptr; // @SAMPLE_EDIT (removed static)
static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr;
static IDXGISwapChain* g_pSwapChain = nullptr;
static bool g_SwapChainOccluded = false;
static UINT g_ResizeWidth = 0, g_ResizeHeight = 0;
static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr;
// Forward declarations of helper functions
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Main code
int main(int, char**) {
// Make process DPI aware and obtain main monitor scale
ImGui_ImplWin32_EnableDpiAwareness();
float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(
::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY));
// Create application window
WNDCLASSEXW wc = {sizeof(wc),
CS_CLASSDC,
WndProc,
0L,
0L,
GetModuleHandle(nullptr),
nullptr,
nullptr,
nullptr,
nullptr,
L"ImGui Example",
nullptr};
::RegisterClassExW(&wc);
HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example",
WS_OVERLAPPEDWINDOW, 100, 100,
(int)(1280 * main_scale), (int)(800 * main_scale),
nullptr, nullptr, wc.hInstance, nullptr);
// Initialize Direct3D
if (!CreateDeviceD3D(hwnd)) {
CleanupDeviceD3D();
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
// Show the window
::ShowWindow(hwnd, SW_SHOWDEFAULT);
::UpdateWindow(hwnd);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
// io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable
// Multi-Viewport / Platform Windows // @SAMPLE_EDIT disabled temporarily
// io.ConfigViewportsNoAutoMerge = true;
// io.ConfigViewportsNoTaskBarIcon = true;
// io.ConfigViewportsNoDefaultParent = true;
// io.ConfigDockingAlwaysTabBar = true;
// io.ConfigDockingTransparentPayload = true;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsLight();
// Setup scaling
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(
main_scale); // Bake a fixed style scale. (until we have a solution for
// dynamic style scaling, changing this requires resetting
// Style + calling this again)
style.FontScaleDpi =
main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true
// makes this unnecessary. We leave both here for
// documentation purpose)
io.ConfigDpiScaleFonts =
true; // [Experimental] Automatically overwrite style.FontScaleDpi in
// Begin() when Monitor DPI changes. This will scale fonts but
// _NOT_ scale sizes/padding for now.
io.ConfigDpiScaleViewports =
true; // [Experimental] Scale Dear ImGui and Platform Windows when
// Monitor DPI changes.
// When viewports are enabled we tweak WindowRounding/WindowBg so platform
// windows can look identical to regular ones.
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
// Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can
// also load multiple fonts and use ImGui::PushFont()/PopFont() to select
// them.
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you
// need to select the font among multiple.
// - If the file cannot be loaded, the function will return a nullptr. Please
// handle those errors in your application (e.g. use an assertion, or display
// an error and quit).
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype
// for higher quality font rendering.
// - Read 'docs/FONTS.md' for more instructions and details.
// - Remember that in C/C++ if you want to include a backslash \ in a string
// literal you need to write a double backslash \\ !
// style.FontSizeBase = 20.0f;
// io.Fonts->AddFontDefault();
// io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf");
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf");
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf");
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf");
// ImFont* font =
// io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf");
// IM_ASSERT(font != nullptr);
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
bool done = false;
Sample::Base& sample = GetSample(); // @SAMPLE_EDIT
done = !sample.Startup(); // @SAMPLE_EDIT
while (!done) {
// Poll and handle messages (inputs, window resize, etc.)
// See the WndProc() function below for our to dispatch events to the Win32
// backend.
MSG msg;
while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT) done = true;
}
if (done) break;
// Handle window being minimized or screen locked
if (g_SwapChainOccluded &&
g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) {
::Sleep(10);
continue;
}
g_SwapChainOccluded = false;
// Handle window resize (we don't resize directly in the WM_SIZE handler)
if (g_ResizeWidth != 0 && g_ResizeHeight != 0) {
CleanupRenderTarget();
g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight,
DXGI_FORMAT_UNKNOWN, 0);
g_ResizeWidth = g_ResizeHeight = 0;
CreateRenderTarget();
}
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
#if 0 // @SAMPLE_EDIT
ImGui::NewFrame();
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
// Rendering
ImGui::Render();
const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
// Update and Render additional Platform Windows
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
#endif // @SAMPLE_EDIT
//=========================================================================================
// @SAMPLE_EDIT EDIT TO ORIGINAL IMGUI main.cpp
IM_UNUSED(show_demo_window);
IM_UNUSED(show_another_window);
// Avoids high CPU/GPU usage by releasing this thread until enough time has
// passed
static auto sLastTime = std::chrono::steady_clock::now();
std::chrono::duration<float> elapsedSec =
std::chrono::steady_clock::now() - sLastTime;
if (elapsedSec.count() < 1.f / 120.f) {
std::this_thread::sleep_for(std::chrono::microseconds(250));
continue;
}
// Draw the Local Imgui UI and remote imgui UI
sLastTime = std::chrono::steady_clock::now();
const float clear_color_with_alpha[4] = {
clear_color.x * clear_color.w, clear_color.y * clear_color.w,
clear_color.z * clear_color.w, clear_color.w};
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView,
clear_color_with_alpha);
sample.Draw();
if (ImGui::GetDrawData()) {
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
// Update and render additional Platform Windows
static int sLastFrame = -1;
int newFrame = ImGui::GetFrameCount();
if (sLastFrame != newFrame &&
io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
sLastFrame = newFrame;
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
//=========================================================================================
// Present
// HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync
HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
}
// Cleanup
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
sample.Shutdown(); // @SAMPLE_EDIT
ImGui::DestroyContext();
CleanupDeviceD3D();
::DestroyWindow(hwnd);
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 0;
}
// Helper functions
bool CreateDeviceD3D(HWND hWnd) {
// Setup swap chain
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 2;
sd.BufferDesc.Width = 0;
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
UINT createDeviceFlags = 0;
// createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[2] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0,
};
HRESULT res = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software
// driver if hardware is not available.
res = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags,
featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
if (res != S_OK) return false;
CreateRenderTarget();
return true;
}
void CleanupDeviceD3D() {
CleanupRenderTarget();
if (g_pSwapChain) {
g_pSwapChain->Release();
g_pSwapChain = nullptr;
}
if (g_pd3dDeviceContext) {
g_pd3dDeviceContext->Release();
g_pd3dDeviceContext = nullptr;
}
if (g_pd3dDevice) {
g_pd3dDevice->Release();
g_pd3dDevice = nullptr;
}
}
void CreateRenderTarget() {
ID3D11Texture2D* pBackBuffer;
g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr,
&g_mainRenderTargetView);
pBackBuffer->Release();
}
void CleanupRenderTarget() {
if (g_mainRenderTargetView) {
g_mainRenderTargetView->Release();
g_mainRenderTargetView = nullptr;
}
}
// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
// Win32 message handler
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if
// dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your
// main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to
// your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from
// your application based on those two flags.
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return true;
switch (msg) {
case WM_SIZE:
if (wParam == SIZE_MINIMIZED) return 0;
g_ResizeWidth = (UINT)LOWORD(lParam); // Queue resize
g_ResizeHeight = (UINT)HIWORD(lParam);
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
return 0;
break;
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd) {
IM_UNUSED(hInstance);
IM_UNUSED(hPrevInstance);
IM_UNUSED(lpCmdLine);
IM_UNUSED(nShowCmd);
return main(0, nullptr);
}
//=================================================================================================
@@ -0,0 +1,198 @@
// Adapted for Google: removed NETIMGUI_IMPLEMENTATION (sources compiled
// separately), updated include paths for third_party layout.
//=================================================================================================
// SAMPLE NO BACKEND
//-------------------------------------------------------------------------------------------------
// Demonstration of using Dear ImGui without any Backend support.
// This way, user can use DearImgui without any code needed for drawing / input
// management, since it is all handled by the NetImgui remote server instead.
// Useful when this code is running on hardward without any display and/or
// convenient input.
//
// Because we are not using any Backend code, this Sample is a little bit
// different from the others. All of its code is included in this file (except
// for Dear ImGui sources) and does not rely on some shared sample source file.
//
// This sample compile both 'Dear Imgui' and 'NetImgui' sources directly
// (not using their project version in the solution)
//
// NOTE: This sample is also use in backward compatibility test with
// older Dear ImGui versions,
// making it easier to compile Dear Imgui without any OS
//specific code (Backends)
//
// NOTE: This is also an excellent example of own little is needed to add
// NetImgui support
// to a project. It doesn't handle Font DPI regeneration,
//keeping things simple.
//=================================================================================================
#include <stdio.h>
#include <chrono>
#include <thread>
#include "NetImgui_Api.h"
#include "Source/Fonts/Roboto_Medium.cpp"
namespace SampleNoBackend {
enum eSampleState : uint8_t {
Start,
Disconnected,
Connected,
};
//=================================================================================================
// Initialize the Dear Imgui Context and the NetImgui library
//=================================================================================================
bool Client_Startup() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.BackendFlags |=
ImGuiBackendFlags_HasGamepad; // Enable NetImgui Gamepad support
io.DisplaySize = ImVec2(8, 8);
ImFontConfig FontConfig = {};
const char FontName[] = "Roboto Medium";
memcpy(FontConfig.Name, FontName, sizeof(FontName));
io.Fonts->AddFontFromMemoryCompressedTTF(Roboto_Medium_compressed_data,
Roboto_Medium_compressed_size, 16.f,
&FontConfig);
#if !NETIMGUI_IMGUI_TEXTURES_ENABLED
io.Fonts->Build();
io.Fonts->SetTexID(0);
#endif
ImGui::StyleColorsDark();
if (!NetImgui::Startup()) return false;
return true;
}
//=================================================================================================
// Release resources
//=================================================================================================
void Client_Shutdown() {
NetImgui::Shutdown();
ImGui::DestroyContext(ImGui::GetCurrentContext());
}
//=================================================================================================
// Manage connection to NetImguiServer
//=================================================================================================
void Client_Connect(eSampleState& sampleState) {
constexpr char zClientName[] = "SampleNoBackend (ImGui " IMGUI_VERSION ")";
if (sampleState == eSampleState::Start) {
printf("- Connecting to NetImguiServer to (127.0.0.1:%i)... ",
NetImgui::kDefaultServerPort);
NetImgui::ConnectToApp(zClientName, "localhost");
while (NetImgui::IsConnectionPending());
bool bSuccess = NetImgui::IsConnected();
sampleState =
bSuccess ? eSampleState::Connected : eSampleState::Disconnected;
printf(bSuccess ? "Success\n" : "Failed\n");
if (!bSuccess) {
printf("- Waiting for a connection from NetImguiServer on port %i... ",
NetImgui::kDefaultClientPort);
NetImgui::ConnectFromApp(zClientName);
}
} else if (sampleState == eSampleState::Disconnected &&
NetImgui::IsConnected()) {
sampleState = eSampleState::Connected;
printf("CONNECTED\n");
} else if (sampleState == eSampleState::Connected &&
!NetImgui::IsConnected()) {
sampleState = eSampleState::Disconnected;
printf("DISCONNECTED\n");
printf("- Waiting for a connection from NetImguiServer on port %i... ",
NetImgui::kDefaultClientPort);
NetImgui::ConnectFromApp(zClientName);
}
}
//=================================================================================================
// Render all of our Dear ImGui Content (when appropriate)
//=================================================================================================
void Client_Draw(bool& bQuit) {
if (NetImgui::IsConnected() && NetImgui::NewFrame(true)) {
ImGui::ShowDemoWindow();
ImGui::SetNextWindowPos(ImVec2(32, 48), ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
if (ImGui::Begin("Sample No Backend", nullptr)) {
ImGui::TextColored(ImVec4(0.1, 1, 0.1, 1), "Client:");
ImGui::TextUnformatted(" DearImgui Version: " IMGUI_VERSION);
ImGui::TextUnformatted(" NetImgui Version: " NETIMGUI_VERSION);
ImGui::TextUnformatted("");
ImGui::SameLine();
bQuit = ImGui::Button(" Quit ");
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Terminate this sample.");
ImGui::NewLine();
ImGui::TextColored(ImVec4(0.1, 1, 0.1, 1), "Description:");
ImGui::TextWrapped(
"This sample demonstate the ability to use Dear ImGui without any "
"Backend. "
"Rely instead on NetImgui to remotely handle drawing and inputs. "
"This avoids the need for rendering/input/window management code on "
"the client itself.");
}
ImGui::End();
NetImgui::EndFrame();
}
}
} // namespace SampleNoBackend
int main(int, char**) {
printf(
"========================================================================"
"========\n");
printf(" NetImgui Sample: No Backend\n");
printf(
"========================================================================"
"========\n");
printf(
" Demonstrate 'Dear ImGui' + 'NetImgui' for a UI displayed on a remote "
"server.\n");
printf(" Dear ImGui : " IMGUI_VERSION "\n");
printf(" NetImgui : " NETIMGUI_VERSION "\n");
printf(" ['Ctrl + C' to quit]\n");
printf(
"------------------------------------------------------------------------"
"--------\n");
if (!SampleNoBackend::Client_Startup()) {
printf("Failed initializing NetImgui.");
SampleNoBackend::Client_Shutdown();
return -1;
}
// Main loop
bool bQuit = false;
SampleNoBackend::eSampleState sampleState =
SampleNoBackend::eSampleState::Start;
while (!bQuit) {
// Avoids high CPU/GPU usage by releasing this thread until enough time has
// passed
static auto sLastTime = std::chrono::steady_clock::now();
std::chrono::duration<float> elapsedSec =
std::chrono::steady_clock::now() - sLastTime;
if (elapsedSec.count() < 1.f / 120.f) {
std::this_thread::sleep_for(std::chrono::microseconds(250));
continue;
}
sLastTime = std::chrono::steady_clock::now();
// Sample Update
SampleNoBackend::Client_Connect(sampleState);
SampleNoBackend::Client_Draw(bQuit);
}
// Cleanup
SampleNoBackend::Client_Shutdown();
return 0;
}
@@ -0,0 +1,417 @@
#include "NetImguiServer_App.h"
#include "Fonts/Roboto_Medium.cpp"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_Network.h"
#include "NetImguiServer_RemoteClient.h"
#include "NetImguiServer_UI.h"
namespace NetImguiServer {
namespace App {
constexpr uint32_t kClientCountMax =
32; //! @sammyfreg todo: support unlimited client count
ImVector<ServerTexture*>
gServerTextures; // List of ALL server created textures (used by server and
// clients)
ServerTexture* gServerTextureEmpty =
nullptr; // Empty texture used when no valid texture found
bool gLoadedConfigOnce = false;
void UpdateServerTextures();
bool Startup(const char* CmdLine) {
//---------------------------------------------------------------------------------------------
// Load Settings savefile and parse for auto connect commandline option
//---------------------------------------------------------------------------------------------
if (!gLoadedConfigOnce) {
NetImguiServer::Config::Client::LoadAll();
gLoadedConfigOnce = true;
}
AddTransientClientConfigFromString(CmdLine);
//---------------------------------------------------------------------------------------------
// Perform application initialization:
//---------------------------------------------------------------------------------------------
if (RemoteClient::Client::Startup(kClientCountMax) &&
NetImguiServer::Network::Startup() && NetImguiServer::UI::Startup()) {
NetImgui::Internal::CmdTexture cmdTexture;
uint32_t EmptyData[4 * 4] = {
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF,
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF,
0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF};
cmdTexture.mTextureClientID = 0;
cmdTexture.mFormat = ImTextureFormat::ImTextureFormat_RGBA32;
cmdTexture.mWidth = cmdTexture.mHeight = 4;
cmdTexture.mpTextureData.SetPtr((uint8_t*)EmptyData);
gServerTextureEmpty = CreateTexture(cmdTexture, sizeof(EmptyData));
LoadFonts();
ImGui::GetIO().IniFilename =
nullptr; // Disable server ImGui ini settings (not really needed, and
// avoid imgui.ini filename conflicts)
ImGui::GetIO().LogFilename = nullptr;
return HAL_Startup(CmdLine);
}
return false;
}
void Shutdown() {
// Mark all texture resources as wanting deletion
for (ServerTexture* texServer : gServerTextures) {
if (texServer && texServer->IsValid()) {
if (!texServer->mIsCustom) {
texServer->mTexData.SetStatus(ImTextureStatus_WantDestroy);
texServer->mTexData.UnusedFrames = 1;
} else {
#if TEXTURE_CUSTOM_SAMPLE
if (texServer && texServer->mIsCustom) {
0 // TODO
}
#endif
}
}
}
// Allow Dear ImGui to delete the textures
ImGui::NewFrame();
ImGui::Render();
HAL_RenderDrawData(ImGui::GetDrawData());
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
// Finish removing them from Dear ImGui user textures and delete objects
UpdateServerTextures();
// Update Dear ImGui texture list (so it doesn't have deleted items in it)
ImGui::NewFrame();
ImGui::Render();
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
// Remove deleted textures from clients
for (uint32_t i(0); i < RemoteClient::Client::GetCountMax(); ++i) {
RemoteClient::Client& client = RemoteClient::Client::Get(i);
client.mTextureTable.clear();
}
NetImguiServer::Network::Shutdown();
NetImguiServer::UI::Shutdown();
NetImguiServer::Config::Client::Clear();
RemoteClient::Client::Shutdown();
HAL_Shutdown();
}
//=================================================================================================
// INIT CLIENT CONFIG FROM STRING
// Take a commandline string, and create a ClientConfig from it.
// Simple format of (Hostname);(HostPort)
bool AddTransientClientConfigFromString(const char* string)
//=================================================================================================
{
NetImguiServer::Config::Client cmdlineClient;
const char* zEntryStart = string;
const char* zEntryCur = string;
int paramIndex = 0;
cmdlineClient.mConfigType =
NetImguiServer::Config::Client::eConfigType::Transient;
NetImgui::Internal::StringCopy(cmdlineClient.mClientName, "Commandline");
while (*zEntryCur != 0) {
zEntryCur++;
// Skip commandline preamble holding path to executable
if (*zEntryCur == ' ' && *(zEntryCur + 1) != 0) {
zEntryStart = zEntryCur + 1;
}
if ((*zEntryCur == ';' || *zEntryCur == 0)) {
if (paramIndex == 0)
NetImgui::Internal::StringCopy(cmdlineClient.mHostName, zEntryStart,
zEntryCur - zEntryStart);
else if (paramIndex == 1)
cmdlineClient.mHostPort = static_cast<uint32_t>(atoi(zEntryStart));
cmdlineClient.mConnectAuto =
paramIndex >=
1; // Mark valid for connexion as soon as we have a HostAddress
zEntryStart = zEntryCur + 1;
paramIndex++;
}
}
if (cmdlineClient.mConnectAuto) {
NetImguiServer::Config::Client::SetConfig(cmdlineClient);
return true;
}
return false;
}
//=================================================================================================
// DRAW CLIENT BACKGROUND
// Create a separate Dear ImGui drawing context, to generate a commandlist that
// will fill the RenderTarget with a specific background picture
void DrawClientBackground(RemoteClient::Client& client)
//=================================================================================================
{
NetImgui::Internal::CmdBackground* pBGUpdate =
client.mPendingBackgroundIn.Release();
if (pBGUpdate) {
client.mBGSettings = *pBGUpdate;
client.mBGNeedUpdate = true;
netImguiDeleteSafe(pBGUpdate);
}
if (client.mpBGContext == nullptr) {
client.mpBGContext = ImGui::CreateContext(ImGui::GetIO().Fonts);
client.mpBGContext->IO.DeltaTime = 1 / 30.f;
client.mpBGContext->IO.IniFilename =
nullptr; // Disable server ImGui ini settings (not really needed, and
// avoid imgui.ini filename conflicts)
client.mpBGContext->IO.LogFilename = nullptr;
client.mpBGContext->IO.BackendFlags |=
ImGuiBackendFlags_RendererHasTextures;
}
// Detect when the main font textures was updated,
// in which case we need to re-generate the BG DrawData
const ImVector<ImTextureData*>& MainTextures =
ImGui::GetPlatformIO().Textures;
NetImgui::Internal::ScopedImguiContext scopedCtx(client.mpBGContext);
const ImVector<ImTextureData*>& BGTextures = ImGui::GetPlatformIO().Textures;
for (ImTextureData* texData : BGTextures) {
client.mBGNeedUpdate |= MainTextures.contains(texData) == false;
}
// Update the BG DrawData
// Note: Maybe we should try rendering it directly in client windows every
// frame instead, to keep things simpler
if (client.mBGNeedUpdate) {
ImGui::GetIO().DisplaySize = ImVec2(client.mAreaSizeX, client.mAreaSizeY);
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(client.mAreaSizeX, client.mAreaSizeY));
ImGui::Begin("Background", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoSavedSettings);
// Look for the desired texture (and use default if not found)
auto texIt = client.mTextureTable.find(client.mBGSettings.mTextureId);
const ServerTexture* pTexture = texIt == client.mTextureTable.end()
? UI::GetBackgroundTexture()
: texIt->second;
UI::DrawCenteredBackground(pTexture,
ImVec4(client.mBGSettings.mTextureTint[0],
client.mBGSettings.mTextureTint[1],
client.mBGSettings.mTextureTint[2],
client.mBGSettings.mTextureTint[3]));
ImGui::End();
ImGui::Render();
client.mBGNeedUpdate = false;
}
}
//=================================================================================================
void UpdateServerTextures()
//=================================================================================================
{
for (int i(gServerTextures.size() - 1); i >= 0; --i) {
if (gServerTextures[i]) {
ServerTexture& ServerTex = *gServerTextures[i];
// Release un-needed pixel data once it has been processed by backend
if (ServerTex.mIsUpdatable == false &&
ServerTex.mTexData.Pixels != nullptr &&
ServerTex.mTexData.Status == ImTextureStatus_OK) {
ServerTex.mTexData.DestroyPixels();
}
// Backend deleted the texture, remove it from our list
else if (ServerTex.mTexData.Status == ImTextureStatus_Destroyed) {
ImGui::UnregisterUserTexture(&ServerTex.mTexData);
delete gServerTextures[i];
gServerTextures[i] = nullptr;
}
// Send deletion request to backend
else if (ServerTex.mTexData.WantDestroyNextFrame) {
const RemoteClient::Client* Client =
ServerTex.mOwnerClientIndex >= 0
? &RemoteClient::Client::Get(ServerTex.mOwnerClientIndex)
: nullptr;
if (!Client || !Client->mpImguiDrawData ||
Client->mpImguiDrawData->mFrameIndex > ServerTex.mLastFrameUsed) {
if (ServerTex.mTexData.UnusedFrames++ > 0) {
ServerTex.mTexData.Status = ImTextureStatus_WantDestroy;
}
}
}
}
// Remove release textures (null) from our list
if (gServerTextures[i] == nullptr) {
ImSwap(gServerTextures[i], gServerTextures[gServerTextures.Size - 1]);
gServerTextures.pop_back();
}
}
}
//=================================================================================================
// Default texture creation behavior, relying on Dear ImGui backend to do
// the heavy lifting of texture creation and management
bool CreateTexture_Default(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize)
//=================================================================================================
{
if (!serverTexture.mIsCustom && cmdTexture.mpTextureData.Get() != nullptr) {
serverTexture.mIsUpdatable = cmdTexture.mUpdatable;
serverTexture.mTexData.Create(ImTextureFormat::ImTextureFormat_RGBA32,
cmdTexture.mWidth, cmdTexture.mHeight);
if (cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_RGBA32) {
memcpy(serverTexture.mTexData.Pixels, cmdTexture.mpTextureData.Get(),
customDataSize);
} else if (cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_Alpha8) {
const uint8_t* pSrcCur = cmdTexture.mpTextureData.Get();
uint32_t* pDestCur =
reinterpret_cast<uint32_t*>(serverTexture.mTexData.GetPixels());
uint32_t* pDestEnd = &pDestCur[cmdTexture.mHeight * cmdTexture.mWidth];
while (pDestCur < pDestEnd) {
*pDestCur++ = 0x00FFFFFF | (uint64_t(*pSrcCur++) << 24);
}
} else {
IM_ASSERT_USER_ERROR(0, "Unsupported format");
}
serverTexture.mTexData.Status = ImTextureStatus_WantCreate;
serverTexture.mTexData.UseColors =
cmdTexture.mFormat == ImTextureFormat::ImTextureFormat_RGBA32;
ImGui::RegisterUserTexture(&serverTexture.mTexData);
return true;
}
return false;
}
//=================================================================================================
ServerTexture* CreateTexture(const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t textureDataSize)
//=================================================================================================
{
ServerTexture* serverTex(new ServerTexture);
if (serverTex) {
serverTex->mIsCustom =
cmdTexture.mFormat == NetImgui::eTexFormat::kTexFmtCustom;
serverTex->mClientTexID = cmdTexture.mTextureClientID;
serverTex->mIsUpdatable = false;
if (CreateTexture_Custom(*serverTex, cmdTexture, textureDataSize) ||
CreateTexture_Default(*serverTex, cmdTexture, textureDataSize)) {
gServerTextures.push_back(serverTex);
} else {
delete serverTex;
serverTex = NULL;
}
}
return serverTex;
}
//=================================================================================================
// Initialize all needed fonts by the NetImguiServer application
void LoadFonts()
//=================================================================================================
{
ImFontConfig fontConfig;
ImFontAtlas* pFontAtlas = ImGui::GetIO().Fonts;
// Add Fonts here...
// Using a different default font (provided with Dear ImGui)
NetImgui::Internal::StringCopy(fontConfig.Name, "Roboto Medium");
if (!pFontAtlas->AddFontFromMemoryCompressedTTF(Roboto_Medium_compressed_data,
Roboto_Medium_compressed_size,
0.f, &fontConfig)) {
pFontAtlas->AddFontDefault(&fontConfig);
}
}
//=================================================================================================
void UpdateWindowPlacement(int x, int y, int w, int h, bool isMaximized)
//=================================================================================================
{
NetImguiServer::Config::Server::sWindowPlacement[0] = x;
NetImguiServer::Config::Server::sWindowPlacement[1] = y;
NetImguiServer::Config::Server::sWindowPlacement[2] = w;
NetImguiServer::Config::Server::sWindowPlacement[3] = h;
NetImguiServer::Config::Server::sWindowMaximized = isMaximized;
NetImguiServer::Config::Client::SaveAll();
}
//=================================================================================================
WindowPlacement GetWindowPlacement()
//=================================================================================================
{
if (!gLoadedConfigOnce) {
NetImguiServer::Config::Client::LoadAll();
gLoadedConfigOnce = true;
}
WindowPlacement wp;
wp.x = NetImguiServer::Config::Server::sWindowPlacement[0];
wp.y = NetImguiServer::Config::Server::sWindowPlacement[1];
wp.w = ImMax(100, NetImguiServer::Config::Server::sWindowPlacement[2]);
wp.h = ImMax(100, NetImguiServer::Config::Server::sWindowPlacement[3]);
wp.isMaximized = NetImguiServer::Config::Server::sWindowMaximized;
return wp;
}
//=================================================================================================
// UPDATE REMOTE CONTENT
// Create a render target for each connected remote client once, and update it
// every frame with the last drawing commands received from it. This Render
// Target will then be used normally as the background image of each client
// window renderered by this Server
void UpdateClientDraw()
//=================================================================================================
{
for (uint32_t i(0); i < RemoteClient::Client::GetCountMax(); ++i) {
RemoteClient::Client& client = RemoteClient::Client::Get(i);
if (client.mbIsConnected) {
if (client.mbIsReleased) {
client.Uninitialize();
} else {
client.ProcessPendingTextureCmds();
if (client.mbIsVisible) {
// Update the RenderTarget destination of each client, of size was
// updated
if (client.mAreaSizeX > 0 && client.mAreaSizeY > 0 &&
(!client.mpHAL_AreaRT ||
client.mAreaRTSizeX != client.mAreaSizeX ||
client.mAreaRTSizeY != client.mAreaSizeY)) {
if (HAL_CreateRenderTarget(client.mAreaSizeX, client.mAreaSizeY,
client.mpHAL_AreaRT,
client.mHAL_AreaTexture)) {
client.mAreaRTSizeX = client.mAreaSizeX;
client.mAreaRTSizeY = client.mAreaSizeY;
client.mLastUpdateTime =
std::chrono::steady_clock::now() -
std::chrono::hours(1); // Will redraw the client
client.mBGNeedUpdate = true;
}
}
// Render the remote results
ImDrawData* pDrawData =
client.GetImguiDrawData(gServerTextureEmpty->mTexData.GetTexID());
if (pDrawData) {
DrawClientBackground(client);
HAL_RenderDrawData(client, pDrawData);
}
}
}
}
}
UpdateServerTextures();
}
} // namespace App
} // namespace NetImguiServer
@@ -0,0 +1,144 @@
// Google modifications:
// - Added #ifndef guards around HAL_API_PLATFORM_* defines so they can be
// overridden from BUILD-level defines (-D flags).
#pragma once
#include <Private/NetImgui_Network.h>
#include <Private/NetImgui_Shared.h>
//=============================================================================================
// SELECT RENDERING/OS API HERE
//=============================================================================================
#ifndef HAL_API_PLATFORM_WIN32_DX11
#define HAL_API_PLATFORM_WIN32_DX11 1
#endif
#ifndef HAL_API_PLATFORM_GLFW_GL3
#define HAL_API_PLATFORM_GLFW_GL3 0 // Note: Currently doesn't work on VS2026
#endif
#ifndef HAL_API_PLATFORM_SOKOL
#define HAL_API_PLATFORM_SOKOL \
0 // Sokol Lib needs to be updated to latest Dear ImGui 1.92 support for this
// to work
#endif
#define HAL_API_RENDERTARGET_INVERT_Y \
(HAL_API_PLATFORM_GLFW_GL3 || \
HAL_API_PLATFORM_SOKOL) // Invert client render target Y axis (since OpenGL
// start texture UV from BottomLeft instead of
// DirectX TopLeft)
//=============================================================================================
// Forward declare
namespace NetImguiServer {
namespace RemoteClient {
struct Client;
}
} // namespace NetImguiServer
namespace NetImgui {
namespace Internal {
struct CmdTexture;
}
} // namespace NetImgui
namespace NetImguiServer {
namespace App {
struct WindowPlacement {
int x = 0;
int y = 0;
int w = 0;
int h = 0;
bool isMaximized = false;
};
//=============================================================================================
// Code specific to 'NetImgui Server' application and needed inside platform
// specific code
//=============================================================================================
// Additional initialisation needed by 'NetImGui Server' and not part of default
// ImGui sample code
bool Startup(const char* CmdLine);
// Prepare for shutdown of application
void Shutdown();
// Receive rendering request of each Remote client and output it to their own
// RenderTarget
void UpdateClientDraw();
// Save server window placement (to restore it on next start)
void UpdateWindowPlacement(int x, int y, int w, int h, bool isMaximized);
// Get last server window placement
WindowPlacement GetWindowPlacement();
// Add a new remote client config to our list (to attempt connexion)
bool AddTransientClientConfigFromString(const char* string);
// Initialize the font atlas used by the Serve
void LoadFonts();
// Descriptor of each textures by Server. Format always RGBA8
struct ServerTexture {
inline ServerTexture() {
mTexData.Status = ImTextureStatus_Destroyed;
mTexData.RefCount = 1;
}
inline bool IsValid() const {
return mTexData.Status != ImTextureStatus_WantCreate &&
mTexData.Status != ImTextureStatus_Destroyed;
}
inline void MarkForDelete() {
mTexData.WantDestroyNextFrame = true;
mTexData.UnusedFrames = 0;
}
ImTextureData mTexData; // Struct used by backend for texture support
ImTextureID mClientTexID; // Client UserID associated with this texture
uint64_t mCustomData = 0u; // Memory available to custom command
uint64_t mLastFrameUsed = 0u; // Last draw frame this texture was used
// (needed for resources release)
int32_t mOwnerClientIndex = -1; // Client that created this texture (if any)
uint8_t mIsCustom = 0u; // Format handled by custom version of NetImguiServer
// modified by library user
uint8_t mIsUpdatable = 0u; // True when textures can be updated (font)
uint8_t mPadding[6] = {};
};
//=============================================================================================
// Handling of texture data
//=============================================================================================
ServerTexture* CreateTexture(const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
// Library users can implement their own texture format (on client/server).
// Useful for vidoe streaming, new format, etc.
bool CreateTexture_Custom(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
bool DestroyTexture_Custom(ServerTexture& serverTexture,
const NetImgui::Internal::CmdTexture& cmdTexture,
uint32_t customDataSize);
//=============================================================================================
// Note (H)ardware (A)bstraction (L)ayer
// When porting the 'NetImgui Server' application to other
//platform, theses are the few functions needed to be supported by each specific
//API that are not already supported by de 'Dear ImGui' provided backends
//=============================================================================================
// Additional initialisation that are platform specific
bool HAL_Startup(const char* CmdLine);
// Prepare for shutdown of application, with platform specific code
void HAL_Shutdown();
// Receive a platform specific socket, and return us with info on the connection
bool HAL_GetSocketInfo(NetImgui::Internal::Network::SocketInfo* pClientSocket,
char* pOutHostname, size_t HostNameLen, int& outPort);
// Provide the current user setting folder (used to save the shared config file)
const char* HAL_GetUserSettingFolder();
// Return true when new content should be retrieved from Clipboard (avoid
// constantly reading/converting content)
bool HAL_GetClipboardUpdated();
// Receive a ImDrawData drawlist and render it to backbuffer
void HAL_RenderDrawData(ImDrawData* pDrawData);
// Receive a ImDrawData drawlist and request Dear ImGui's backend to output it
// into a texture
void HAL_RenderDrawData(RemoteClient::Client& client, ImDrawData* pDrawData);
// Allocate a RenderTarget that each client will use to output their ImGui
// drawing into.
bool HAL_CreateRenderTarget(uint16_t Width, uint16_t Height, void*& pOutRT,
ImTextureData& OutTexture);
// Free a RenderTarget resource
void HAL_DestroyRenderTarget(void*& pOutRT, ImTextureData& OutTexture);
} // namespace App
} // namespace NetImguiServer
@@ -0,0 +1,441 @@
// Google modifications:
// - Updated nlohmann/json include path for Google third_party layout.
#include "NetImguiServer_Config.h"
#include <NetImgui_Api.h>
#include <fstream>
#include <mutex>
#include <nlohmann/json.hpp>
#include "NetImguiServer_RemoteClient.h"
namespace NetImguiServer {
namespace Config {
static ImVector<Client*> gConfigList;
static std::mutex gConfigLock;
static Client::RuntimeID gRuntimeID = static_cast<Client::RuntimeID>(1);
static constexpr char kConfigField_ServerPort[] = "ServerPort";
static constexpr char kConfigField_ServerRefreshActive[] = "RefreshFPSActive";
static constexpr char kConfigField_ServerRefreshInactive[] =
"RefreshFPSInactive";
static constexpr char kConfigField_ServerDPIScaleRatio[] = "DPIScaleRatio";
static constexpr char kConfigField_ServerCompressionEnable[] =
"CompressionEnable";
static constexpr char kConfigField_ServerFontSize[] = "ServerFontSize";
static constexpr char kConfigField_ServerWindowPlacementX[] =
"ServerWindowPlacementX";
static constexpr char kConfigField_ServerWindowPlacementY[] =
"ServerWindowPlacementY";
static constexpr char kConfigField_ServerWindowPlacementW[] =
"ServerWindowPlacementW";
static constexpr char kConfigField_ServerWindowPlacementH[] =
"ServerWindowPlacementH";
static constexpr char kConfigField_ServerWindowMaximized[] =
"ServerWindowMaximized";
static constexpr char kConfigField_Note[] = "Note";
static constexpr char kConfigField_Version[] = "Version";
static constexpr char kConfigField_Configs[] = "Configs";
static constexpr char kConfigField_Name[] = "Name";
static constexpr char kConfigField_Hostname[] = "Hostname";
static constexpr char kConfigField_Hostport[] = "HostPort";
static constexpr char kConfigField_AutoConnect[] = "Auto";
static constexpr char kConfigField_BlockTakeover[] = "BlockTakeover";
static constexpr char kConfigField_DPIScaleEnabled[] = "DPIScaleEnabled";
uint32_t Server::sPort = NetImgui::kDefaultServerPort;
float Server::sRefreshFPSActive = 30.f;
float Server::sRefreshFPSInactive = 30.f;
float Server::sDPIScaleRatio = 1.f;
bool Server::sCompressionEnable = true;
float Server::sFontSize = 16.f;
int Server::sWindowPlacement[4] = {100, 100, 1280, 1024};
bool Server::sWindowMaximized = false;
//=================================================================================================
// The user config is created when the main config file is readonly
// Allows having a distributed config file that cannot be touched by users
static const char* GetConfigFilename(Client::eConfigType configFileType)
//=================================================================================================
{
if (configFileType == Client::eConfigType::Local) {
return "netImgui.cfg";
} else if (configFileType == Client::eConfigType::Local2nd) {
return "netImgui_2.cfg";
} else {
static char sUserSettingFile[1024];
const char* userSettingFolder =
NetImguiServer::App::HAL_GetUserSettingFolder();
NetImgui::Internal::StringFormat(
sUserSettingFile,
(userSettingFolder ? "%s\\netImgui.cfg" : "netImgui_2.cfg"),
userSettingFolder);
return sUserSettingFile;
}
}
//=================================================================================================
// Find entry index with same configId. (-1 if not found)
// Note: 'gConfigLock' should have already locked before calling this
static int FindClientIndex(uint32_t configID)
//=================================================================================================
{
if (configID != Client::kInvalidRuntimeID) {
for (int i(0); i < gConfigList.size(); ++i) {
if (gConfigList[i] && gConfigList[i]->mRuntimeID == configID) return i;
}
}
return -1;
}
//=================================================================================================
template <typename TType>
TType GetPropertyValue(const nlohmann::json& config, const char* zPropertyName,
const TType& valueDefault)
//=================================================================================================
{
const auto& valueNode = config.find(zPropertyName);
return valueNode != config.end() ? valueNode->get<TType>() : valueDefault;
}
//=================================================================================================
Client::Client()
//=================================================================================================
: mHostPort(NetImgui::kDefaultClientPort),
mRuntimeID(kInvalidRuntimeID),
mConfigType(NetImguiServer::Config::Client::eConfigType::Pending),
mDPIScaleEnabled(true),
mBlockTakeover(false),
mReadOnly(false),
mConnectAuto(false),
mConnectRequest(false),
mConnectForce(false),
mConnectStatus(eStatus::Disconnected) {
NetImgui::Internal::StringCopy(mClientName, "New Client");
NetImgui::Internal::StringCopy(mHostName, "localhost");
}
//=================================================================================================
void Client::SetConfig(const Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
// Only allow 1 transient connection to keep things clean
for (int i = 0; config.IsTransient() && i < gConfigList.size(); ++i) {
if (gConfigList[i] && gConfigList[i]->IsTransient()) {
NetImgui::Internal::netImguiDelete(gConfigList[i]);
gConfigList.erase(&gConfigList[i]);
}
}
int index = FindClientIndex(config.mRuntimeID);
// Config not found, add it to our list
if (index == -1) {
index = gConfigList.size();
gConfigList.push_back(
NetImgui::Internal::netImguiNew<NetImguiServer::Config::Client>());
}
// Update the entry
*gConfigList[index] = config;
gConfigList[index]->mRuntimeID =
(config.mRuntimeID == kInvalidRuntimeID ? gRuntimeID++
: config.mRuntimeID);
}
//=================================================================================================
void Client::DelConfig(uint32_t configID)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
NetImgui::Internal::netImguiDelete(gConfigList[index]);
gConfigList.erase(&gConfigList[index]);
}
}
//=================================================================================================
bool Client::GetConfigByID(uint32_t configID, Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
config = *gConfigList[index];
return true;
}
return false;
}
//=================================================================================================
bool Client::GetConfigByIndex(uint32_t index, Client& config)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
if (index < static_cast<uint32_t>(gConfigList.size())) {
config = *gConfigList[index];
return true;
}
return false;
}
//=================================================================================================
uint32_t Client::GetConfigCount()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
return gConfigList.size();
}
//=================================================================================================
bool Client::GetProperty_BlockTakeover(uint32_t configID)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
return gConfigList[index]->mBlockTakeover;
}
return false;
}
//=================================================================================================
void Client::SetProperty_Status(uint32_t configID, eStatus Status)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectStatus = Status;
}
}
//=================================================================================================
void Client::SetProperty_ConnectAuto(uint32_t configID, bool value)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectAuto = value;
}
}
//=================================================================================================
void Client::SetProperty_ConnectRequest(uint32_t configID, bool value,
bool force)
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
int index = FindClientIndex(configID);
if (index != -1) {
gConfigList[index]->mConnectRequest = value && !force;
gConfigList[index]->mConnectForce = value && force;
gConfigList[index]->mConnectLastTime = std::chrono::steady_clock::now();
}
}
//=================================================================================================
bool Client::ShouldSave(eConfigType fileConfigType) const
//=================================================================================================
{
return mConfigType == fileConfigType || mConfigType == eConfigType::Pending;
}
//=================================================================================================
void Client::SaveAll()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
std::ofstream localFile(GetConfigFilename(eConfigType::Local));
bool localIsWritable = localFile.is_open();
localFile.close();
std::ifstream local2ndFileExist(GetConfigFilename(eConfigType::Local2nd));
bool local2ndExist = local2ndFileExist.is_open();
bool local2ndIsWritable = false;
local2ndFileExist.close();
// Try saving into default config file
SaveConfigFile(eConfigType::Local, localIsWritable);
// And then in 2nd workingdir user file when 1st one is read only
if (!localIsWritable || local2ndExist) {
std::ofstream local2ndFile(GetConfigFilename(eConfigType::Local2nd));
SaveConfigFile(eConfigType::Local2nd,
!localIsWritable && local2ndIsWritable);
}
// Finally saved the shared config into user folder
SaveConfigFile(eConfigType::Shared, !localIsWritable && !local2ndIsWritable);
}
//=================================================================================================
void Client::SaveConfigFile(eConfigType configFileType,
bool writeServerSettings)
//=================================================================================================
{
nlohmann::json configRoot;
configRoot[kConfigField_Version] = eVersion::_Latest;
configRoot[kConfigField_Note] =
configFileType == eConfigType::Local
? "NetImgui Server's list of Clients (Using JSON format) Local File."
: configFileType == eConfigType::Local
? "NetImgui Server's list of Clients (Using JSON format) 2nd Local "
"File."
: "NetImgui Server's list of Clients (Using JSON format) Shared "
"File.";
if (writeServerSettings) {
configRoot[kConfigField_ServerPort] = Server::sPort;
configRoot[kConfigField_ServerRefreshActive] = Server::sRefreshFPSActive;
configRoot[kConfigField_ServerRefreshInactive] =
Server::sRefreshFPSInactive;
configRoot[kConfigField_ServerDPIScaleRatio] = Server::sDPIScaleRatio;
configRoot[kConfigField_ServerCompressionEnable] =
Server::sCompressionEnable;
configRoot[kConfigField_ServerFontSize] = Server::sFontSize;
configRoot[kConfigField_ServerWindowPlacementX] =
Server::sWindowPlacement[0];
configRoot[kConfigField_ServerWindowPlacementY] =
Server::sWindowPlacement[1];
configRoot[kConfigField_ServerWindowPlacementW] =
Server::sWindowPlacement[2];
configRoot[kConfigField_ServerWindowPlacementH] =
Server::sWindowPlacement[3];
configRoot[kConfigField_ServerWindowMaximized] = Server::sWindowMaximized;
}
int clientToSaveCount(0);
for (int i(0); i < gConfigList.size(); ++i) {
Client* pConfig = gConfigList[i];
if (pConfig && pConfig->ShouldSave(configFileType)) {
auto& config = configRoot[kConfigField_Configs][clientToSaveCount++] =
nullptr;
config[kConfigField_Name] = pConfig->mClientName;
config[kConfigField_Hostname] = pConfig->mHostName;
config[kConfigField_Hostport] = pConfig->mHostPort;
config[kConfigField_AutoConnect] = pConfig->mConnectAuto;
config[kConfigField_BlockTakeover] = pConfig->mBlockTakeover;
config[kConfigField_DPIScaleEnabled] = pConfig->mDPIScaleEnabled;
}
}
std::ofstream outputFile(GetConfigFilename(configFileType));
if (outputFile.is_open()) {
outputFile << configRoot.dump(4);
for (int i(0); clientToSaveCount > 0 && i < gConfigList.size(); ++i) {
Client* pConfig = gConfigList[i];
if (pConfig && pConfig->ShouldSave(configFileType)) {
pConfig->mReadOnly = false;
pConfig->mConfigType = configFileType;
}
}
}
}
//=================================================================================================
void Client::LoadAll()
//=================================================================================================
{
Clear();
std::lock_guard<std::mutex> guard(gConfigLock);
LoadConfigFile(eConfigType::Local);
LoadConfigFile(eConfigType::Local2nd);
LoadConfigFile(eConfigType::Shared);
}
//=================================================================================================
void Client::LoadConfigFile(eConfigType configFileType)
//=================================================================================================
{
nlohmann::json configRoot;
const char* filename = GetConfigFilename(configFileType);
std::ifstream inputFile(filename);
if (!inputFile.is_open() || inputFile.eof()) return;
configRoot = nlohmann::json::parse(inputFile, nullptr, false);
inputFile.close();
std::ofstream outputFile(filename, std::ios_base::app);
bool isWritable = outputFile.is_open() && !outputFile.eof();
outputFile.close();
uint32_t configVersion =
GetPropertyValue(configRoot, kConfigField_Version, 0u);
Server::sPort = GetPropertyValue(configRoot, kConfigField_ServerPort,
NetImgui::kDefaultServerPort);
Server::sRefreshFPSActive = GetPropertyValue(
configRoot, kConfigField_ServerRefreshActive, Server::sRefreshFPSActive);
Server::sRefreshFPSInactive =
GetPropertyValue(configRoot, kConfigField_ServerRefreshInactive,
Server::sRefreshFPSInactive);
Server::sDPIScaleRatio = GetPropertyValue(
configRoot, kConfigField_ServerDPIScaleRatio, Server::sDPIScaleRatio);
Server::sCompressionEnable =
GetPropertyValue(configRoot, kConfigField_ServerCompressionEnable,
Server::sCompressionEnable);
Server::sFontSize = GetPropertyValue(configRoot, kConfigField_ServerFontSize,
Server::sFontSize);
Server::sWindowPlacement[0] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementX,
Server::sWindowPlacement[0]);
Server::sWindowPlacement[1] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementY,
Server::sWindowPlacement[1]);
Server::sWindowPlacement[2] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementW,
Server::sWindowPlacement[2]);
Server::sWindowPlacement[3] =
GetPropertyValue(configRoot, kConfigField_ServerWindowPlacementH,
Server::sWindowPlacement[3]);
Server::sWindowMaximized = GetPropertyValue(
configRoot, kConfigField_ServerWindowMaximized, Server::sWindowMaximized);
if (configVersion >= static_cast<uint32_t>(eVersion::Initial)) {
for (const auto& config : configRoot[kConfigField_Configs]) {
gConfigList.push_back(
NetImgui::Internal::netImguiNew<NetImguiServer::Config::Client>());
Client* pConfig = gConfigList.back();
pConfig->mRuntimeID = gRuntimeID++;
if (config.find(kConfigField_Name) != config.end())
NetImgui::Internal::StringCopy(
pConfig->mClientName,
config[kConfigField_Name].get<std::string>().c_str());
if (config.find(kConfigField_Hostname) != config.end())
NetImgui::Internal::StringCopy(
pConfig->mHostName,
config[kConfigField_Hostname].get<std::string>().c_str());
pConfig->mHostPort =
GetPropertyValue(config, kConfigField_Hostport, pConfig->mHostPort);
pConfig->mConnectAuto = GetPropertyValue(config, kConfigField_AutoConnect,
pConfig->mConnectAuto);
pConfig->mDPIScaleEnabled = GetPropertyValue(
config, kConfigField_DPIScaleEnabled, pConfig->mDPIScaleEnabled);
pConfig->mBlockTakeover = GetPropertyValue(
config, kConfigField_BlockTakeover, pConfig->mBlockTakeover);
pConfig->mConfigType = configFileType;
pConfig->mReadOnly = !isWritable;
}
}
}
//=================================================================================================
void Client::Clear()
//=================================================================================================
{
std::lock_guard<std::mutex> guard(gConfigLock);
while (gConfigList.size()) {
NetImgui::Internal::netImguiDelete(gConfigList.back());
gConfigList.pop_back();
}
}
} // namespace Config
} // namespace NetImguiServer
@@ -0,0 +1,160 @@
// Google modifications:
// - Added #include <chrono> for std::chrono::steady_clock::time_point
#pragma once
#include <chrono>
#include <stdint.h>
namespace NetImguiServer {
namespace Config {
//=================================================================================================
// Client Configs are used by this server to reach remote netImgui Clients.
//
// Note:For multihreading safety, we are always working with copies of the
// original data.
//
// Note:It is also possible for a remote netImgui client to connect to Server
// directly,
// in which case they don't need to have an associated config.
//=================================================================================================
class Client {
public:
using RuntimeID = uint32_t;
using TimeStamp = std::chrono::steady_clock::time_point;
static constexpr RuntimeID kInvalidRuntimeID = static_cast<RuntimeID>(0);
enum class eVersion : uint32_t {
Initial = 1, // First version save file deployed
Refresh = 2, // Added refresh rate support
DPIScale = 3, // Added DPI scaling
BlockTakeOver = 4, // Added Takeover Block
WindowPlacement = 5, // Added saving of main window location and size
_Count,
_Latest = _Count - 1
};
enum class eConfigType : uint8_t {
Pending, // New config, will try saving it in the local config
Local, // Config fetched from local config file, in the current working
// directory
Local2nd, // Config fetched from a second local config file, in the current
// working directory. Used when 'Local' file is read only
Shared, // Config fetched from the shared user folder
Transient, // Config created from connection request (command line, OS
// pipes), cannot be saved
};
enum class eStatus : uint8_t {
Disconnected, // No connection detected on client
Connecting, // This server is connecting to client
Connected, // This server is connect to client
Available, // Client already taken, but this server can take over
ErrorBusy, // Client already taken
ErrorVer, // Server/Client network api mismatch
};
// Config settings
char mClientName[128]; //!< Client display name
char mHostName[128]; //!< Client IP or remote host address to attempt
//!< connection at
uint32_t mHostPort; //!< Client Port to attempt connection at
RuntimeID mRuntimeID; //!< Unique RuntimeID used to find this Config
eConfigType mConfigType; //!< Type of the configuration
bool mDPIScaleEnabled; //!< Enable support of Font DPI scaling requests by
//!< Server
bool mBlockTakeover; //!< If another NetImguiServer is allowed to forcefully
//!< disconnect this client to connect to it
bool mReadOnly; //!< Config comes from read only file, can't be modified
bool mConnectAuto; //!< Try automatically connecting to client
// Transient values used while running
mutable bool
mConnectRequest; //!< Attempt connecting to Client, after user request
mutable bool mConnectForce; //!< Attempt connecting to Client, after user
//!< request, even if already connected
mutable eStatus mConnectStatus; //!< Connection status of associated client
mutable TimeStamp mConnectLastTime; //!< Last connection attempt time (avoid
//!< quickly retrying same client)
// Access methods
public:
Client();
inline bool IsReadOnly() const { return mReadOnly; };
inline bool IsTransient() const {
return mConfigType == eConfigType::Transient;
};
inline bool IsConnected() const {
return mConnectStatus == eStatus::Connected;
}
inline bool IsConnecting() const {
return mConnectStatus == eStatus::Connecting || mConnectRequest ||
mConnectForce;
}
inline bool IsConnectReady() const {
bool bAvailable = mConnectStatus != eStatus::Connected &&
mConnectStatus != eStatus::Connecting;
return bAvailable && (mConnectRequest || mConnectForce);
}
inline bool IsAutoConnectReady() const {
bool bAvailable = mConnectStatus != eStatus::Connected &&
mConnectStatus != eStatus::Connecting;
auto elapsedTime = std::chrono::steady_clock::now() - mConnectLastTime;
int durationSec = static_cast<int>(
std::chrono::duration_cast<std::chrono::seconds>(elapsedTime).count());
bool elapseOk =
durationSec > (mConnectStatus == eStatus::Disconnected ? 5 : 30);
return bAvailable && mConnectAuto && elapseOk;
}
// Add/Edit/Remove config
static void SetConfig(
const Client& config); //!< Add or replace a client configuration info
static void DelConfig(uint32_t configID); //!< Remove a client configuration
static bool GetConfigByID(
uint32_t configID,
Client& outConfig); //!< Find client configuration with this id (return
//!< true if found)
static bool GetConfigByIndex(
uint32_t index,
Client& outConfig); //!< Find client configuration at the x position
static uint32_t GetConfigCount();
// Set property value directly (without having to copy entire structure)
static bool GetProperty_BlockTakeover(uint32_t configID);
static void SetProperty_Status(uint32_t configID, eStatus Status);
static void SetProperty_ConnectAuto(uint32_t configID, bool value);
static void SetProperty_ConnectRequest(uint32_t configID, bool value,
bool force);
// Client Config list management
static void SaveAll();
static void LoadAll();
static void Clear();
protected:
static void SaveConfigFile(eConfigType fileConfigType,
bool writeServerSettings);
static void LoadConfigFile(eConfigType fileConfigType);
inline bool ShouldSave(eConfigType fileConfigType) const;
};
struct Server {
static uint32_t sPort; //!< Port that Server should use for connection.
//!< (Note: not really a 'Client' setting, but easier
//!< to just bundle the value here for the moment)
static float sRefreshFPSActive; //!< Refresh rate of active Window
static float sRefreshFPSInactive; //!< Refresh rate of inactive Window
static float
sDPIScaleRatio; //!< Ratio of DPI scale applied to Font size (helps with
//!< high resolution monitor, default 1.0)
static bool sCompressionEnable; //!< Ask the clients to compress their data
//!< before transmission
static float sFontSize; //!< Font size used for Server UI
static int sWindowPlacement[4]; //!< Main window position and size
//!< (x,y,width,height)
static bool sWindowMaximized;
};
} // namespace Config
} // namespace NetImguiServer
@@ -0,0 +1,577 @@
#include "NetImguiServer_Network.h"
#include <Private/NetImgui_CmdPackets.h>
#include <Private/NetImgui_Network.h>
#include <thread>
#include "NetImguiServer_App.h"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_RemoteClient.h"
#include "Private/NetImgui_WarningDisableStd.h"
using namespace NetImgui::Internal;
namespace NetImguiServer {
namespace Network {
using atomic_SocketInfo = std::atomic<::Network::SocketInfo*>;
static bool gbShutdown(false); // Set to true when NetImguiServer exiting
static atomic_SocketInfo gListenSocket(
nullptr); // Need global access to kill socket on shutdown
static std::atomic_uint32_t gActiveClientThreadCount(
0); // How many active client connection currently running
static std::atomic_bool gActiveThreadConnectOut(
false); // True while Server is still trying to connect to new clients
static std::atomic_bool gActiveThreadConnectIn(
false); // True while Server is still trying to receive connection from new
// clients
static std::atomic_uint64_t gStatsDataSent(0);
static std::atomic_uint64_t gStatsDataRcvd(0);
//=================================================================================================
// (IN) COMMAND TEXTURE
//=================================================================================================
void Communications_Incoming_CmdTexture(RemoteClient::Client& Client) {
auto pCmdTexture = reinterpret_cast<NetImgui::Internal::CmdTexture*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdTexture->mpTextureData.ToPointer();
// For debug tracking
uint32_t idx =
Client.mTextureHistoryIndex % IM_ARRAYSIZE(Client.mTextureHistory);
Client.mTextureHistory[idx].UpdateId = Client.mTextureHistoryIndex++;
Client.mTextureHistory[idx].Frame = Client.mLastDrawFrameIndex;
Client.mTextureHistory[idx].ClientId = pCmdTexture->mTextureClientID;
Client.mTextureHistory[idx].Format = pCmdTexture->mFormat;
Client.mTextureHistory[idx].isCreate =
pCmdTexture->mStatus == CmdTexture::eType::Create;
Client.mTextureHistory[idx].isDestroy =
pCmdTexture->mStatus == CmdTexture::eType::Destroy;
Client.mTextureHistory[idx].isUpdate =
pCmdTexture->mStatus == CmdTexture::eType::Update;
Client.mTextureHistory[idx].isDearImguiManaged =
pCmdTexture->mIsDearImGuiManaged != 0;
Client.mTextureHistory[idx].x = pCmdTexture->mOffsetX;
Client.mTextureHistory[idx].y = pCmdTexture->mOffsetY;
Client.mTextureHistory[idx].w = pCmdTexture->mWidth;
Client.mTextureHistory[idx].h = pCmdTexture->mHeight;
Client.ReceiveTexture(pCmdTexture);
}
//=================================================================================================
// (IN) COMMAND BACKGROUND
//=================================================================================================
void Communications_Incoming_CmdBackground(RemoteClient::Client& Client) {
auto pCmdBackground = reinterpret_cast<NetImgui::Internal::CmdBackground*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
Client.mPendingBackgroundIn.Assign(pCmdBackground);
}
//=================================================================================================
// (IN) COMMAND DRAW FRAME
//=================================================================================================
void Communications_Incoming_CmdDrawFrame(RemoteClient::Client& Client) {
auto pCmdDraw = reinterpret_cast<NetImgui::Internal::CmdDrawFrame*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdDraw->ToPointers();
Client.ReceiveDrawFrame(pCmdDraw);
}
//=================================================================================================
// (IN) COMMAND CLIPBOARD
//=================================================================================================
void Communications_Incoming_CmdClipboard(RemoteClient::Client& Client) {
auto pCmdClipboard = reinterpret_cast<NetImgui::Internal::CmdClipboard*>(
Client.mPendingRcv.pCommand);
Client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdClipboard->ToPointers();
Client.mPendingClipboardIn.Assign(pCmdClipboard);
}
//=================================================================================================
// Receive every commands sent by remote client and process them
//=================================================================================================
void Communications_Incoming(RemoteClient::Client& Client) {
if (::Network::DataReceivePending(Client.mpSocket)) {
//-----------------------------------------------------------------------------------------
// 1. Ready to receive new command, starts the process by reading Header
//-----------------------------------------------------------------------------------------
if (Client.mPendingRcv.IsReady()) {
Client.mCmdPendingRead = NetImgui::Internal::CmdPendingRead();
Client.mPendingRcv.pCommand = &Client.mCmdPendingRead;
Client.mPendingRcv.bAutoFree = false;
}
//-----------------------------------------------------------------------------------------
// 2. Read incoming command from server
//-----------------------------------------------------------------------------------------
if (Client.mPendingRcv.IsPending()) {
::Network::DataReceive(Client.mpSocket, Client.mPendingRcv);
// Detected a new command bigger than header, allocate memory for it
if (Client.mPendingRcv.pCommand->mSize >
sizeof(NetImgui::Internal::CmdPendingRead) &&
Client.mPendingRcv.pCommand == &Client.mCmdPendingRead) {
CmdPendingRead* pCmdHeader =
reinterpret_cast<NetImgui::Internal::CmdPendingRead*>(
netImguiSizedNew<uint8_t>(Client.mPendingRcv.pCommand->mSize));
*pCmdHeader = Client.mCmdPendingRead;
Client.mPendingRcv.pCommand = pCmdHeader;
Client.mPendingRcv.bAutoFree = true;
}
}
//-----------------------------------------------------------------------------------------
// 3. Command fully received from Server, process it
//-----------------------------------------------------------------------------------------
if (Client.mPendingRcv.IsDone()) {
if (!Client.mPendingRcv.IsError()) {
Client.mStatsDataRcvd += Client.mPendingRcv.pCommand->mSize;
Client.mLastIncomingComTime = std::chrono::steady_clock::now();
switch (Client.mPendingRcv.pCommand->mType) {
case NetImgui::Internal::CmdHeader::eCommands::Texture:
Communications_Incoming_CmdTexture(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::Background:
Communications_Incoming_CmdBackground(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::DrawFrame:
Communications_Incoming_CmdDrawFrame(Client);
break;
case NetImgui::Internal::CmdHeader::eCommands::Clipboard:
Communications_Incoming_CmdClipboard(Client);
break;
// Commands not received in main loop, by Server
case NetImgui::Internal::CmdHeader::eCommands::Version:
case NetImgui::Internal::CmdHeader::eCommands::Input:
case NetImgui::Internal::CmdHeader::eCommands::Count:
break;
}
}
// Reset pending read
if (Client.mPendingRcv.IsError()) {
Client.mbDisconnectPending = true;
}
if (Client.mPendingRcv.bAutoFree) {
netImguiDeleteSafe(Client.mPendingRcv.pCommand);
}
Client.mPendingRcv = PendingCom();
}
}
// Prevent high CPU usage when waiting for new data
else {
// std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::microseconds(250));
}
}
//=================================================================================================
// Send the updates to RemoteClient
// Ends with a Ping Command (signal a end of commands)
//=================================================================================================
void Communications_Outgoing(RemoteClient::Client& Client) {
//---------------------------------------------------------------------------------------------
// Try finishing sending a pending command to Server
//---------------------------------------------------------------------------------------------
if (Client.mPendingSend.IsPending()) {
::Network::DataSend(Client.mpSocket, Client.mPendingSend);
// Free allocated memory for command
if (Client.mPendingSend.IsDone()) {
if (Client.mPendingSend.IsError()) {
Client.mbDisconnectPending = true;
}
Client.mStatsDataSent += Client.mPendingSend.pCommand->mSize;
if (Client.mPendingSend.bAutoFree) {
netImguiDeleteSafe(Client.mPendingSend.pCommand);
}
Client.mPendingSend = PendingCom();
}
}
if (Client.mPendingSend.IsReady()) {
NetImgui::Internal::CmdClipboard* pClipboardCmd =
Client.TakePendingClipboard();
if (pClipboardCmd) {
pClipboardCmd->ToOffsets();
Client.mPendingSend.pCommand = pClipboardCmd;
Client.mPendingSend.bAutoFree = true;
}
}
if (Client.mPendingSend.IsReady()) {
NetImgui::Internal::CmdInput* pInputCmd = Client.TakePendingInput();
Client.mPendingSend.pCommand = pInputCmd;
Client.mPendingSend.bAutoFree = true;
}
}
//=================================================================================================
// Update communications stats of a client, after a frame
//=================================================================================================
void Communications_UpdateClientStats(RemoteClient::Client& Client) {
// Update data transfer stats
auto elapsedTime = std::chrono::steady_clock::now() - Client.mStatsTime;
if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTime)
.count() >= 250) {
constexpr uint64_t kHysteresis = 10; // out of 100
uint64_t newDataRcvd = Client.mStatsDataRcvd - Client.mStatsDataRcvdPrev;
uint64_t newDataSent = Client.mStatsDataSent - Client.mStatsDataSentPrev;
uint64_t tmMicrosS =
std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime)
.count();
uint32_t newDataRcvdBps =
static_cast<uint32_t>(newDataRcvd * 1000000u / tmMicrosS);
uint32_t newDataSentBps =
static_cast<uint32_t>(newDataSent * 1000000u / tmMicrosS);
Client.mStatsRcvdBps = (Client.mStatsRcvdBps * (100u - kHysteresis) +
newDataRcvdBps * kHysteresis) /
100u;
Client.mStatsSentBps = (Client.mStatsSentBps * (100u - kHysteresis) +
newDataSentBps * kHysteresis) /
100u;
gStatsDataRcvd += newDataRcvd;
gStatsDataSent += newDataSent;
Client.mStatsTime = std::chrono::steady_clock::now();
Client.mStatsDataRcvdPrev = Client.mStatsDataRcvd;
Client.mStatsDataSentPrev = Client.mStatsDataSent;
}
}
//=================================================================================================
// Keep sending/receiving commands to Remote Client, until disconnection occurs
//=================================================================================================
void Communications_ClientExchangeLoop(RemoteClient::Client* pClient) {
gActiveClientThreadCount++;
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Connected);
pClient->mbDisconnectPending = false;
pClient->mbIsConnected = true;
while (!gbShutdown && !pClient->mbDisconnectPending) {
Communications_Outgoing(*pClient);
Communications_Incoming(*pClient);
Communications_UpdateClientStats(*pClient);
}
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
NetImgui::Internal::Network::Disconnect(pClient->mpSocket);
pClient->Release();
gActiveClientThreadCount--;
}
//=================================================================================================
// Establish connection with Remote Client
// Makes sure that Server/Client are compatible
//=================================================================================================
bool Communications_InitializeClient(
NetImgui::Internal::Network::SocketInfo* pClientSocket,
RemoteClient::Client* pClient, bool ConnectForce) {
NetImgui::Internal::CmdVersion cmdVersionSend, cmdVersionRcv;
NetImgui::Internal::PendingCom PendingRcv, PendingSend;
//---------------------------------------------------------------------
// Handshake confirming connection validity
//---------------------------------------------------------------------
NetImgui::Internal::StringCopy(cmdVersionSend.mClientName, "Server");
const bool ConnectExclusive =
NetImguiServer::Config::Client::GetProperty_BlockTakeover(
pClient->mClientConfigID);
cmdVersionSend.mFlags |=
ConnectExclusive
? static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::ConnectExclusive)
: 0;
cmdVersionSend.mFlags |=
ConnectForce ? static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::ConnectForce)
: 0;
PendingSend.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionSend);
while (!gbShutdown && !PendingSend.IsDone()) {
::Network::DataSend(pClientSocket, PendingSend);
}
if (!PendingSend.IsError()) {
PendingRcv.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionRcv);
while (!PendingRcv.IsDone() &&
cmdVersionRcv.mType == CmdHeader::eCommands::Version) {
while (!gbShutdown && !::Network::DataReceivePending(pClientSocket)) {
std::this_thread::yield(); // Idle until we receive the remote data
}
::Network::DataReceive(pClientSocket, PendingRcv);
}
if (!gbShutdown && !PendingRcv.IsError()) {
//---------------------------------------------------------------------
// Connection accepted, initialize client
//---------------------------------------------------------------------
if (cmdVersionRcv.mType !=
NetImgui::Internal::CmdHeader::eCommands::Version ||
cmdVersionRcv.mVersion !=
NetImgui::Internal::CmdVersion::eVersion::_current) {
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::ErrorVer);
return false;
} else if (cmdVersionRcv.mFlags &
static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::IsConnected)) {
bool bAvailable =
(cmdVersionRcv.mFlags &
static_cast<uint8_t>(
NetImgui::Internal::CmdVersion::eFlags::IsUnavailable)) == 0;
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
bAvailable ? NetImguiServer::Config::Client::eStatus::Available
: NetImguiServer::Config::Client::eStatus::ErrorBusy);
return false;
}
pClient->Initialize();
pClient->mInfoImguiVerID = cmdVersionRcv.mImguiVerID;
pClient->mInfoNetImguiVerID = cmdVersionRcv.mNetImguiVerID;
pClient->mPendingRcv = PendingCom();
pClient->mPendingSend = PendingCom();
NetImgui::Internal::StringCopy(pClient->mInfoName,
cmdVersionRcv.mClientName);
NetImgui::Internal::StringCopy(pClient->mInfoImguiVerName,
cmdVersionRcv.mImguiVerName);
NetImgui::Internal::StringCopy(pClient->mInfoNetImguiVerName,
cmdVersionRcv.mNetImguiVerName);
NetImguiServer::Config::Client clientConfig;
if (NetImguiServer::Config::Client::GetConfigByID(
pClient->mClientConfigID, clientConfig)) {
NetImgui::Internal::StringFormat(
pClient->mWindowID, "%s (%s)##%i", pClient->mInfoName,
clientConfig.mClientName,
static_cast<int>(pClient->mClientIndex)); // Using ClientIndex as a
// window unique ID
} else {
NetImgui::Internal::StringFormat(
pClient->mWindowID, "%s##%i", pClient->mInfoName,
static_cast<int>(pClient->mClientIndex)); // Using ClientIndex as a
// window unique ID
}
return true;
}
}
NetImguiServer::Config::Client::SetProperty_Status(
pClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
return false;
}
//=================================================================================================
// New connection Init request.
// Start new communication thread if handshake sucessfull
//=================================================================================================
void NetworkConnectionNew(
NetImgui::Internal::Network::SocketInfo* pClientSocket,
RemoteClient::Client* pNewClient, bool ConnectForce) {
const char* zErrorMsg(nullptr);
if (pNewClient == nullptr) {
zErrorMsg = "Too many connection on server already";
} else {
NetImguiServer::Config::Client::SetProperty_Status(
pNewClient->mClientConfigID,
NetImguiServer::Config::Client::eStatus::Connecting);
if (zErrorMsg == nullptr && !gbShutdown &&
Communications_InitializeClient(pClientSocket, pNewClient,
ConnectForce) == false) {
zErrorMsg = "Initialization failed. Wrong communication version?";
}
}
if (zErrorMsg == nullptr && !gbShutdown) {
pNewClient->mpSocket = pClientSocket;
std::thread(Communications_ClientExchangeLoop, pNewClient).detach();
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
if (!gbShutdown) {
if (pNewClient) {
pNewClient->mbIsFree = true;
printf("Error connecting to client '%s:%i' (%s)\n",
pNewClient->mConnectHost, pNewClient->mConnectPort, zErrorMsg);
} else {
printf("Error connecting to client (%s)\n", zErrorMsg);
}
}
}
}
//=================================================================================================
// Thread waiting on new Client Connection request
//=================================================================================================
void NetworkConnectRequest_Receive() {
uint32_t serverPort(0);
gActiveThreadConnectIn = true;
while (!gbShutdown) {
// Open (and update when needed) listening socket
if (gListenSocket == nullptr ||
serverPort != NetImguiServer::Config::Server::sPort) {
serverPort = NetImguiServer::Config::Server::sPort;
gListenSocket = NetImgui::Internal::Network::ListenStart(serverPort);
if (gListenSocket.load() == nullptr) {
printf("Failed to start connection listen on port : %i", serverPort);
std::this_thread::sleep_for(std::chrono::milliseconds(
500)); // Reduce Server listening socket open attempt frequency
}
}
// Detect connection request from Clients
if (gListenSocket.load() != nullptr) {
NetImgui::Internal::Network::SocketInfo* pClientSocket =
NetImgui::Internal::Network::ListenConnect(gListenSocket.load());
if (pClientSocket) {
uint32_t freeIndex = RemoteClient::Client::GetFreeIndex();
if (freeIndex != RemoteClient::Client::kInvalidClient) {
RemoteClient::Client& newClient =
RemoteClient::Client::Get(freeIndex);
newClient.mClientConfigID =
NetImguiServer::Config::Client::kInvalidRuntimeID;
newClient.mClientIndex = freeIndex;
NetImguiServer::App::HAL_GetSocketInfo(
pClientSocket, newClient.mConnectHost,
sizeof(newClient.mConnectHost), newClient.mConnectPort);
NetworkConnectionNew(pClientSocket, &newClient, false);
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
NetImgui::Internal::Network::SocketInfo* socketDisconnect =
gListenSocket.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(socketDisconnect);
gActiveThreadConnectIn = false;
}
//=================================================================================================
// Thread trying to reach out new Clients with a connection
//=================================================================================================
void NetworkConnectRequest_Send() {
uint64_t loopIndex(0);
NetImguiServer::Config::Client clientConfig;
gActiveThreadConnectOut = true;
while (!gbShutdown) {
uint32_t clientConfigID(NetImguiServer::Config::Client::kInvalidRuntimeID);
NetImgui::Internal::Network::SocketInfo* pClientSocket = nullptr;
// Find next client configuration to attempt connection to
bool ConnectForce = false;
uint64_t configCount =
static_cast<uint64_t>(NetImguiServer::Config::Client::GetConfigCount());
uint32_t configIdx =
configCount ? static_cast<uint32_t>(loopIndex++ % configCount) : 0;
if (NetImguiServer::Config::Client::GetConfigByIndex(configIdx,
clientConfig)) {
ConnectForce = clientConfig.mConnectForce;
if ((clientConfig.IsConnectReady() ||
clientConfig.IsAutoConnectReady()) &&
clientConfig.mHostPort != NetImguiServer::Config::Server::sPort) {
NetImguiServer::Config::Client::SetProperty_ConnectRequest(
clientConfig.mRuntimeID, false,
false); // Reset the Connection request, we are processing it
NetImguiServer::Config::Client::SetProperty_Status(
clientConfig.mRuntimeID,
NetImguiServer::Config::Client::eStatus::Disconnected);
clientConfigID =
clientConfig.mRuntimeID; // Keep track of ClientConfig we are
// attempting to connect to
pClientSocket = NetImgui::Internal::Network::Connect(
clientConfig.mHostName, clientConfig.mHostPort);
}
}
// Connection successful, find an available client slot
if (pClientSocket) {
uint32_t freeIndex = RemoteClient::Client::GetFreeIndex();
if (freeIndex != RemoteClient::Client::kInvalidClient) {
RemoteClient::Client& newClient = RemoteClient::Client::Get(freeIndex);
if (NetImguiServer::Config::Client::GetConfigByID(clientConfigID,
clientConfig)) {
NetImgui::Internal::StringCopy(newClient.mInfoName,
clientConfig.mClientName);
newClient.mConnectPort = clientConfig.mHostPort;
newClient.mClientConfigID = clientConfigID;
newClient.mClientIndex = freeIndex;
}
NetImguiServer::App::HAL_GetSocketInfo(
pClientSocket, newClient.mConnectHost,
sizeof(newClient.mConnectHost), newClient.mConnectPort);
NetworkConnectionNew(pClientSocket, &newClient, ConnectForce);
} else {
NetImgui::Internal::Network::Disconnect(pClientSocket);
NetImguiServer::Config::Client::SetProperty_Status(
clientConfigID,
NetImguiServer::Config::Client::eStatus::Disconnected);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(
500)); // There's already a wait time in Connect attempt, so no need to
// sleep for too long here
}
gActiveThreadConnectOut = false;
}
//=================================================================================================
// Initialize Networking and start listening thread
//=================================================================================================
bool Startup() {
// Relying on shared network implementation for Winsock Init
if (!NetImgui::Internal::Network::Startup()) {
return false;
}
gbShutdown = false;
gActiveClientThreadCount = 0;
std::thread(NetworkConnectRequest_Receive).detach();
std::thread(NetworkConnectRequest_Send).detach();
return true;
}
//=================================================================================================
// Send signal to terminate all communications and wait until all client have
// been released
//=================================================================================================
void Shutdown() {
gbShutdown = true;
NetImgui::Internal::Network::SocketInfo* socketDisconnect =
gListenSocket.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(socketDisconnect);
while (gActiveClientThreadCount > 0 || gActiveThreadConnectIn ||
gActiveThreadConnectOut) {
std::this_thread::yield();
}
NetImgui::Internal::Network::Shutdown();
}
//=================================================================================================
// True when we are listening for client's connection requests
//=================================================================================================
bool IsWaitingForConnection() { return gListenSocket.load() != nullptr; }
//=================================================================================================
// Total amount of data sent to clients since start
//=================================================================================================
uint64_t GetStatsDataSent() { return gStatsDataSent; }
//=================================================================================================
// Total amount of data received from clients since start
//=================================================================================================
uint64_t GetStatsDataRcvd() { return gStatsDataRcvd; }
} // namespace Network
} // namespace NetImguiServer
@@ -0,0 +1,28 @@
// Google modifications:
// - Added #include <cstdint> for uint32_t (not implicitly available in
// Google).
#pragma once
#include <cstdint>
namespace NetImguiServer {
namespace Network {
// Initialize application's networking
bool Startup();
// Shutdown application's networking
void Shutdown();
// True if this application is actively waiting for clients to reach it
// Note: False when unable to open listen socket (probably because port number
// alreay in use)
bool IsWaitingForConnection();
// Total amount of data sent to clients since start
uint64_t GetStatsDataSent();
// Total amount of data received from clients since start
uint64_t GetStatsDataRcvd();
} // namespace Network
} // namespace NetImguiServer
@@ -0,0 +1,664 @@
#include "NetImguiServer_RemoteClient.h"
#include <Private/NetImgui_CmdPackets.h>
#include <algorithm>
#include "NetImguiServer_App.h"
#include "NetImguiServer_Config.h"
#include "NetImguiServer_UI.h"
namespace NetImguiServer {
namespace RemoteClient {
static Client* gpClients =
nullptr; // Table of all potentially connected clients to this server
static uint32_t gClientCountMax = 0;
NetImguiImDrawData::NetImguiImDrawData() : mCommandList(nullptr) {
CmdListsCount = 1; // All draws collapsed in same CmdList
CmdLists.push_back(&mCommandList);
}
Client::Client()
: mPendingTextureReadIndex(0),
mPendingTextureWriteIndex(0),
mbIsFree(true),
mbCompressionSkipOncePending(false),
mbDisconnectPending(false),
mClientConfigID(NetImguiServer::Config::Client::kInvalidRuntimeID) {}
Client::~Client() { Uninitialize(); }
void Client::ReceiveDrawFrame(NetImgui::Internal::CmdDrawFrame* pFrameData) {
if (pFrameData->mCompressed) {
if (mpFrameDrawPrev != nullptr &&
(mpFrameDrawPrev->mFrameIndex + 1) == pFrameData->mFrameIndex) {
NetImgui::Internal::CmdDrawFrame* pUncompressedFrame =
NetImgui::Internal::DecompressCmdDrawFrame(mpFrameDrawPrev,
pFrameData);
netImguiDeleteSafe(pFrameData);
pFrameData = pUncompressedFrame;
}
// Missing previous frame data
// ignore this drawframe and request a new uncompressed one to be able to
// resume display
else {
mbCompressionSkipOncePending = true;
netImguiDeleteSafe(pFrameData);
}
}
netImguiDeleteSafe(mpFrameDrawPrev);
if (pFrameData) {
// Convert DrawFrame command to Dear Imgui DrawData,
// and make it available for main thread to use in rendering
ProcessCmdDrawFrame(pFrameData);
// Update framerate
constexpr float kHysteresis = 0.025f; // Between 0 to 1.0
auto elapsedTime = std::chrono::steady_clock::now() - mLastDrawFrame;
float elapsedMs =
static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime)
.count()) /
1000.f;
mStatsDrawElapsedMs =
mStatsDrawElapsedMs * (1.f - kHysteresis) + elapsedMs * kHysteresis;
mLastDrawFrame = std::chrono::steady_clock::now();
}
}
void Client::ReceiveTexture(NetImgui::Internal::CmdTexture* pTextureCmd) {
if (pTextureCmd) {
// Wait for a free spot in the ring buffer
while (mPendingTextureWriteIndex - mPendingTextureReadIndex >=
IM_ARRAYSIZE(mpPendingTextures)) {
std::this_thread::yield();
}
uint64_t writeIndex =
mPendingTextureWriteIndex % IM_ARRAYSIZE(mpPendingTextures);
mpPendingTextures[writeIndex] = pTextureCmd;
mPendingTextureWriteIndex = mPendingTextureWriteIndex + 1;
}
}
//=================================================================================================
// Process pending texture commands received from Client
//=================================================================================================
void Client::ProcessPendingTextureCmds() {
while (mPendingTextureReadIndex != mPendingTextureWriteIndex) {
uint64_t readIndex =
mPendingTextureReadIndex % IM_ARRAYSIZE(mpPendingTextures);
NetImgui::Internal::CmdTexture* pTextureCmd = mpPendingTextures[readIndex];
auto texIt = mTextureTable.find(pTextureCmd->mTextureClientID);
NetImguiServer::App::ServerTexture* serverTex =
texIt != mTextureTable.end() ? texIt->second : nullptr;
bool isCreate =
pTextureCmd->mStatus == NetImgui::Internal::CmdTexture::eType::Create &&
pTextureCmd->mFormat != NetImgui::eTexFormat::kTexFmt_Invalid;
bool isUpdate =
pTextureCmd->mStatus == NetImgui::Internal::CmdTexture::eType::Update &&
pTextureCmd->mFormat != NetImgui::eTexFormat::kTexFmtCustom;
uint32_t texDataSize =
pTextureCmd->mSize - sizeof(NetImgui::Internal::CmdTexture);
// Delete a texture on request or when creating new one with same
// ClientTextureID
if (!isUpdate && serverTex) {
serverTex->MarkForDelete();
mTextureTable.erase(texIt);
}
// Add a texture
if (isCreate) {
serverTex = NetImguiServer::App::CreateTexture(*pTextureCmd, texDataSize);
if (serverTex) {
serverTex->mOwnerClientIndex = static_cast<int32_t>(mClientIndex);
mTextureTable.insert({pTextureCmd->mTextureClientID, serverTex});
}
}
// Update a Texture
else if (isUpdate && serverTex &&
serverTex->mTexData.Status !=
ImTextureStatus::ImTextureStatus_WantDestroy &&
serverTex->mTexData.Status !=
ImTextureStatus::ImTextureStatus_Destroyed) {
auto TexFormat = static_cast<NetImgui::eTexFormat>(pTextureCmd->mFormat);
if (serverTex->mTexData.Width <
(int)(pTextureCmd->mWidth + pTextureCmd->mOffsetX) ||
serverTex->mTexData.Height <
(int)(pTextureCmd->mHeight + pTextureCmd->mOffsetY)) {
// Update bigger than texture should not happen, but left here as a
// precaution to avoid memory corruption. Could happen if there's an
// error with client/server re-using a texture ID somehow
} else {
size_t SrcLineBytes = NetImgui::GetTexture_BytePerLine(
TexFormat, static_cast<uint32_t>(pTextureCmd->mWidth));
size_t DstLineBytes = NetImgui::GetTexture_BytePerLine(
TexFormat, static_cast<uint32_t>(serverTex->mTexData.Width));
size_t OffsetBytes =
(pTextureCmd->mOffsetY * DstLineBytes) +
NetImgui::GetTexture_BytePerLine(TexFormat, pTextureCmd->mOffsetX);
const uint8_t* pDataSrc = pTextureCmd->mpTextureData.Get();
uint8_t* pDataDst =
reinterpret_cast<uint8_t*>(serverTex->mTexData.GetPixels());
for (uint64_t y(0); y < pTextureCmd->mHeight; ++y) {
memcpy(&pDataDst[OffsetBytes + (y * DstLineBytes)],
&pDataSrc[y * SrcLineBytes], SrcLineBytes);
}
// No need to queue if status is _WantCreate
if (serverTex->mTexData.Status != ImTextureStatus_WantCreate) {
// Following code mostly copied from
// ImFontAtlasTextureBlockQueueUpload
ImTextureData* tex = &serverTex->mTexData;
ImTextureRect req = {(unsigned short)pTextureCmd->mOffsetX,
(unsigned short)pTextureCmd->mOffsetY,
(unsigned short)pTextureCmd->mWidth,
(unsigned short)pTextureCmd->mHeight};
int new_x1 = ImMax(tex->UpdateRect.w == 0
? 0
: tex->UpdateRect.x + tex->UpdateRect.w,
req.x + req.w);
int new_y1 = ImMax(tex->UpdateRect.h == 0
? 0
: tex->UpdateRect.y + tex->UpdateRect.h,
req.y + req.h);
tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x);
tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y);
tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x);
tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y);
tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x);
tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y);
tex->UsedRect.w =
(unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w,
req.x + req.w) -
tex->UsedRect.x);
tex->UsedRect.h =
(unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h,
req.y + req.h) -
tex->UsedRect.y);
tex->Status = ImTextureStatus_WantUpdates;
tex->Updates.push_back(req);
}
}
}
NetImgui::Internal::netImguiDeleteSafe(pTextureCmd);
mPendingTextureReadIndex = mPendingTextureReadIndex + 1;
}
}
void Client::Initialize() {
mConnectedTime = std::chrono::steady_clock::now();
mLastUpdateTime = std::chrono::steady_clock::now() - std::chrono::hours(1);
mLastDrawFrame = std::chrono::steady_clock::now();
mLastIncomingComTime = std::chrono::steady_clock::now();
mLastDrawFrameIndex = 0;
mStatsIndex = 0;
mStatsRcvdBps = 0;
mStatsSentBps = 0;
mStatsDrawElapsedMs = 0.f;
mStatsDataRcvd = 0;
mStatsDataSent = 0;
mStatsDataRcvdPrev = 0;
mStatsDataSentPrev = 0;
mbIsReleased = false;
mStatsTime = std::chrono::steady_clock::now();
mBGSettings =
NetImgui::Internal::CmdBackground(); // Assign background default value,
// until we receive first update
// from client
mPendingRcv = NetImgui::Internal::PendingCom();
mPendingSend = NetImgui::Internal::PendingCom();
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
NetImgui::Internal::netImguiDeleteSafe(mpFrameDrawPrev);
}
void Client::Uninitialize() {
NetImguiServer::App::HAL_DestroyRenderTarget(mpHAL_AreaRT, mHAL_AreaTexture);
for (auto serverTexIt : mTextureTable) {
if (serverTexIt.second) {
serverTexIt.second->MarkForDelete();
}
}
mTextureTable.clear();
mPendingImguiDrawDataIn.Free();
mPendingBackgroundIn.Free();
mPendingInputOut.Free();
mPendingClipboardOut.Free();
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
NetImgui::Internal::netImguiDeleteSafe(mpFrameDrawPrev);
if (mpBGContext) {
ImGui::DestroyContext(mpBGContext);
mpBGContext = nullptr;
}
mInfoName[0] = 0;
mClientIndex = 0;
mClientConfigID = NetImguiServer::Config::Client::kInvalidRuntimeID;
mbCompressionSkipOncePending = false;
mbDisconnectPending = false;
mbIsConnected = false;
mbIsFree = true;
mBGNeedUpdate = true;
}
// Used on communication thread to let main thread know this client resources
// should be deleted
void Client::Release() { mbIsReleased = true; }
bool Client::Startup(uint32_t clientCountMax) {
gClientCountMax = clientCountMax;
gpClients = new Client[clientCountMax];
return gpClients != nullptr;
}
void Client::Shutdown() {
gClientCountMax = 0;
if (gpClients) {
delete[] gpClients;
gpClients = nullptr;
}
}
uint32_t Client::GetCountMax() { return gClientCountMax; }
Client& Client::Get(uint32_t index) {
bool bValid = gpClients && index < gClientCountMax;
static Client sInvalidClient;
assert(bValid);
return bValid ? gpClients[index] : sInvalidClient;
}
uint32_t Client::GetFreeIndex() {
for (uint32_t i(0); i < gClientCountMax; ++i) {
if (gpClients[i].mbIsFree.exchange(false) == true) return i;
}
return kInvalidClient;
}
//=================================================================================================
// Get the current Dear Imgui drawdata to use for this client rendering content
//=================================================================================================
NetImguiImDrawData* Client::GetImguiDrawData(ImTextureID EmtpyTextureID) {
// DrawData's textures should now have been created, safe to use it
if (mpPendingDrawData) {
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
mpImguiDrawData = mpPendingDrawData;
mLastDrawFrameIndex = mpImguiDrawData->mFrameIndex;
mpPendingDrawData = nullptr;
}
// Check if a new frame has been added. If yes, then take ownership of it.
NetImguiImDrawData* pPendingDrawData = mPendingImguiDrawDataIn.Release();
if (pPendingDrawData) {
bool bHasPendingTextureUpdate(false);
// When a new drawdata is available, need to convert the textureid from
// NetImgui Id to the backend renderer format (texture view pointer). Done
// here (in main thread) instead of when first received on the (com thread),
// since 'mvTextures' can only be safely accessed on (main thread).
for (int i(0); i < pPendingDrawData->CmdListsCount; ++i) {
ImDrawList* pCmdList = pPendingDrawData->CmdLists[i];
for (int drawIdx(0), drawCount(pCmdList->CmdBuffer.size());
drawIdx < drawCount; ++drawIdx) {
uint64_t clientTexUserID = pCmdList->CmdBuffer[drawIdx].TexRef._TexID;
auto texIt = mTextureTable.find(clientTexUserID);
ImTextureRef serverTexRef = EmtpyTextureID;
if (texIt != mTextureTable.end() && texIt->second) {
ImTextureData* texData = &texIt->second->mTexData;
serverTexRef = texData->GetTexRef();
texIt->second->mLastFrameUsed =
pPendingDrawData->mFrameIndex; // Needed to know when it is safe
// to release the texture resource
bHasPendingTextureUpdate |=
texData->Status != ImTextureStatus::ImTextureStatus_OK;
}
pCmdList->CmdBuffer[drawIdx].TexRef = serverTexRef;
}
}
// DrawData contains textures with pending updates,
// wait 1 frame to display it
if (bHasPendingTextureUpdate) {
mpPendingDrawData = pPendingDrawData;
}
// New valid DrawData, use it for display
else {
NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData);
mpImguiDrawData = pPendingDrawData;
mLastDrawFrameIndex = mpImguiDrawData->mFrameIndex;
}
}
return mpImguiDrawData;
}
//=================================================================================================
// Create a new Dear Imgui DrawData ready to be submitted for rendering
//=================================================================================================
void Client::ProcessCmdDrawFrame(
NetImgui::Internal::CmdDrawFrame* pCmdDrawFrame) {
constexpr float kPosRangeMin =
static_cast<float>(NetImgui::Internal::ImguiVert::kPosRange_Min);
constexpr float kPosRangeMax =
static_cast<float>(NetImgui::Internal::ImguiVert::kPosRange_Max);
constexpr float kUVRangeMin =
static_cast<float>(NetImgui::Internal::ImguiVert::kUvRange_Min);
constexpr float kUVRangeMax =
static_cast<float>(NetImgui::Internal::ImguiVert::kUvRange_Max);
if (!pCmdDrawFrame) {
return;
}
mMouseCursor = static_cast<ImGuiMouseCursor>(pCmdDrawFrame->mMouseCursor);
NetImguiImDrawData* pDrawData =
NetImgui::Internal::netImguiNew<NetImguiImDrawData>();
pDrawData->mFrameIndex = pCmdDrawFrame->mFrameIndex;
pDrawData->Valid = true;
pDrawData->TotalVtxCount =
static_cast<int>(pCmdDrawFrame->mTotalVerticeCount);
pDrawData->TotalIdxCount = static_cast<int>(pCmdDrawFrame->mTotalIndiceCount);
pDrawData->DisplayPos.x = pCmdDrawFrame->mDisplayArea[0];
pDrawData->DisplayPos.y = pCmdDrawFrame->mDisplayArea[1];
pDrawData->DisplaySize.x =
pCmdDrawFrame->mDisplayArea[2] - pCmdDrawFrame->mDisplayArea[0];
pDrawData->DisplaySize.y =
pCmdDrawFrame->mDisplayArea[3] - pCmdDrawFrame->mDisplayArea[1];
pDrawData->FramebufferScale =
ImVec2(1, 1); //! @sammyfreg Currently untested, so force set to 1
pDrawData->OwnerViewport = nullptr;
ImDrawList* pCmdList = pDrawData->CmdLists[0];
pCmdList->IdxBuffer.resize(pCmdDrawFrame->mTotalIndiceCount);
pCmdList->VtxBuffer.resize(pCmdDrawFrame->mTotalVerticeCount);
pCmdList->CmdBuffer.resize(pCmdDrawFrame->mTotalDrawCount);
pCmdList->Flags =
ImDrawListFlags_AllowVtxOffset | ImDrawListFlags_AntiAliasedLines |
ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_AntiAliasedLinesUseTex;
if (pCmdDrawFrame->mTotalDrawCount != 0) {
uint32_t indexOffset(0), vertexOffset(0);
ImDrawIdx* pIndexDst = &pCmdList->IdxBuffer[0];
ImDrawVert* pVertexDst = &pCmdList->VtxBuffer[0];
ImDrawCmd* pCommandDst = &pCmdList->CmdBuffer[0];
for (uint32_t i(0); i < pCmdDrawFrame->mDrawGroupCount; ++i) {
const NetImgui::Internal::ImguiDrawGroup& drawGroup =
pCmdDrawFrame->mpDrawGroups[i];
// Copy/Convert Indices from network command to Dear ImGui indices format
const uint16_t* pIndices =
reinterpret_cast<const uint16_t*>(drawGroup.mpIndices.Get());
if (drawGroup.mBytePerIndex == sizeof(ImDrawIdx)) {
memcpy(pIndexDst, pIndices, drawGroup.mIndiceCount * sizeof(ImDrawIdx));
} else {
for (uint32_t indexIdx(0); indexIdx < drawGroup.mIndiceCount;
++indexIdx) {
pIndexDst[indexIdx] = static_cast<ImDrawIdx>(pIndices[indexIdx]);
}
}
// Convert the Vertices from network command to Dear Imgui Format
const NetImgui::Internal::ImguiVert* pVertexSrc =
drawGroup.mpVertices.Get();
for (uint32_t vtxIdx(0); vtxIdx < drawGroup.mVerticeCount; ++vtxIdx) {
pVertexDst[vtxIdx].pos.x =
(static_cast<float>(pVertexSrc[vtxIdx].mPos[0]) *
(kPosRangeMax - kPosRangeMin)) /
static_cast<float>(0xFFFF) +
kPosRangeMin + drawGroup.mReferenceCoord[0];
pVertexDst[vtxIdx].pos.y =
(static_cast<float>(pVertexSrc[vtxIdx].mPos[1]) *
(kPosRangeMax - kPosRangeMin)) /
static_cast<float>(0xFFFF) +
kPosRangeMin + drawGroup.mReferenceCoord[1];
pVertexDst[vtxIdx].uv.x =
(static_cast<float>(pVertexSrc[vtxIdx].mUV[0]) *
(kUVRangeMax - kUVRangeMin)) /
static_cast<float>(0xFFFF) +
kUVRangeMin;
pVertexDst[vtxIdx].uv.y =
(static_cast<float>(pVertexSrc[vtxIdx].mUV[1]) *
(kUVRangeMax - kUVRangeMin)) /
static_cast<float>(0xFFFF) +
kUVRangeMin;
pVertexDst[vtxIdx].col = pVertexSrc[vtxIdx].mColor;
}
// Convert the Draws from network command to Dear Imgui Format
const NetImgui::Internal::ImguiDraw* pDrawSrc = drawGroup.mpDraws.Get();
for (uint32_t drawIdx(0); drawIdx < drawGroup.mDrawCount; ++drawIdx) {
pCommandDst[drawIdx].ClipRect.x = pDrawSrc[drawIdx].mClipRect[0];
pCommandDst[drawIdx].ClipRect.y = pDrawSrc[drawIdx].mClipRect[1];
pCommandDst[drawIdx].ClipRect.z = pDrawSrc[drawIdx].mClipRect[2];
pCommandDst[drawIdx].ClipRect.w = pDrawSrc[drawIdx].mClipRect[3];
pCommandDst[drawIdx].VtxOffset =
pDrawSrc[drawIdx].mVtxOffset + vertexOffset;
pCommandDst[drawIdx].IdxOffset =
pDrawSrc[drawIdx].mIdxOffset + indexOffset;
pCommandDst[drawIdx].ElemCount = pDrawSrc[drawIdx].mIdxCount;
pCommandDst[drawIdx].UserCallback = nullptr;
pCommandDst[drawIdx].UserCallbackData = nullptr;
pCommandDst[drawIdx].TexRef._TexID =
NetImgui::Internal::ConvertFromClientTexID(
pDrawSrc[drawIdx].mClientTexId);
}
pIndexDst += drawGroup.mIndiceCount;
pVertexDst += drawGroup.mVerticeCount;
pCommandDst += drawGroup.mDrawCount;
indexOffset += drawGroup.mIndiceCount;
vertexOffset += drawGroup.mVerticeCount;
}
}
mpFrameDrawPrev = pCmdDrawFrame;
mPendingImguiDrawDataIn.Assign(pDrawData);
}
//=================================================================================================
// Note: Caller must take ownership of item and delete the object
//=================================================================================================
NetImgui::Internal::CmdInput* Client::TakePendingInput() {
return mPendingInputOut.Release();
}
NetImgui::Internal::CmdClipboard* Client::TakePendingClipboard() {
return mPendingClipboardOut.Release();
}
//=================================================================================================
// Capture current received Dear ImGui input, and forward it to the active
// client Note: Even if a client is not focused, we are still sending it the
// mouse position,
// so it can update its UI.
// Note: Sending an input command, will trigger a redraw on the client,
// which we receive on the server afterward
//=================================================================================================
void Client::CaptureImguiInput() {
// Capture input from Dear ImGui (when this Client is in focus)
const ImGuiIO& io = ImGui::GetIO();
if (mbIsVisible) {
if (ImGui::IsWindowFocused()) {
const size_t initialSize = mPendingInputChars.size();
const size_t addedChar = io.InputQueueCharacters.size();
if (addedChar) {
mPendingInputChars.resize(initialSize + addedChar);
memcpy(&mPendingInputChars[initialSize], io.InputQueueCharacters.Data,
addedChar * sizeof(ImWchar));
}
mMouseWheelPos[0] += io.MouseWheel;
mMouseWheelPos[1] += io.MouseWheelH;
}
// Update persistent mouse status
if (ImGui::IsMousePosValid(&io.MousePos)) {
mMousePos[0] = io.MousePos.x - ImGui::GetCursorScreenPos().x;
mMousePos[1] = io.MousePos.y - ImGui::GetCursorScreenPos().y;
}
}
// This method is tied to the Server VSync setting, which might not match our
// client desired refresh setting When client refresh drops too much, take
// into consideration the lenght of the Server frame, to evaluate if we should
// update or not
bool wasActive = mbIsActive;
mbIsActive = ImGui::IsWindowFocused();
float clientFPS = !mbIsVisible ? 0.f
: !mbIsActive
? NetImguiServer::Config::Server::sRefreshFPSInactive
: NetImguiServer::Config::Server::sRefreshFPSActive;
float elapsedMs =
static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - mLastUpdateTime)
.count()) /
1000.f;
bool bRefresh = (wasActive != mbIsActive) || elapsedMs > 1000.f / 60.f;
if (!bRefresh) {
return;
}
// Try to re-acquire unsent input command, or create a new one if none pending
NetImgui::Internal::CmdInput* pNewInput = TakePendingInput();
pNewInput =
pNewInput
? pNewInput
: NetImgui::Internal::netImguiNew<NetImgui::Internal::CmdInput>();
// Create new Input command to send to client
NetImguiServer::Config::Client config;
NetImguiServer::Config::Client::GetConfigByID(mClientConfigID, config);
pNewInput->mScreenSize[0] =
static_cast<uint16_t>(ImGui::GetContentRegionAvail().x);
pNewInput->mScreenSize[1] =
static_cast<uint16_t>(ImGui::GetContentRegionAvail().y);
pNewInput->mMousePos[0] = static_cast<int16_t>(mMousePos[0]);
pNewInput->mMousePos[1] = static_cast<int16_t>(mMousePos[1]);
pNewInput->mMouseWheelVert = mMouseWheelPos[0];
pNewInput->mMouseWheelHoriz = mMouseWheelPos[1];
pNewInput->mCompressionUse =
NetImguiServer::Config::Server::sCompressionEnable;
pNewInput->mCompressionSkip = mbCompressionSkipOncePending;
pNewInput->mFontDPIScaling = 1.f;
pNewInput->mDesiredFps = clientFPS;
if (config.mDPIScaleEnabled) {
float scale = ImGui::GetMainViewport()->DpiScale;
scale = scale > 1.f ? scale : 1.f;
pNewInput->mFontDPIScaling =
1.f + (scale - 1.f) * NetImguiServer::Config::Server::sDPIScaleRatio;
}
mbCompressionSkipOncePending = false;
if ((mbIsVisible && mbIsActive) && ImGui::IsWindowFocused()) {
// Mouse Buttons Inputs
// If Dear ImGui Update this enum, must also adjust our enum copy
static_assert(
static_cast<int>(NetImgui::Internal::CmdInput::NetImguiMouseButton::
ImGuiMouseButton_COUNT) ==
static_cast<int>(ImGuiMouseButton_::ImGuiMouseButton_COUNT),
"Update the NetImgui enum to match the updated Dear ImGui enum");
pNewInput->mMouseDownMask = 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Left)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Left
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Right)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Right
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(ImGuiMouseButton_::ImGuiMouseButton_Middle)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Middle
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(3)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Extra1
: 0;
pNewInput->mMouseDownMask |=
ImGui::IsMouseDown(4)
? 1 << NetImgui::Internal::CmdInput::ImGuiMouseButton_Extra2
: 0;
// Keyboard / Gamepads Inputs
// If Dear ImGui Update their enum, must also adjust our enum copy,
// so adding a few check to detect a change
#define EnumKeynameTest(KEYNAME) \
static_cast<int>(NetImgui::Internal::CmdInput::NetImguiKeys::KEYNAME) == \
static_cast<int>(ImGuiKey::KEYNAME - ImGuiKey::ImGuiKey_NamedKey_BEGIN), \
"Update the NetImgui enum to match the updated Dear ImGui enum"
static_assert(
NetImgui::Internal::CmdInput::NetImguiKeys::ImGuiKey_COUNT ==
(ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN),
"Update the NetImgui enum to match the updated Dear ImGui enum");
static_assert(EnumKeynameTest(ImGuiKey_Tab));
static_assert(EnumKeynameTest(ImGuiKey_Escape));
static_assert(EnumKeynameTest(ImGuiKey_RightSuper));
static_assert(EnumKeynameTest(ImGuiKey_Apostrophe));
static_assert(EnumKeynameTest(ImGuiKey_Keypad0));
static_assert(EnumKeynameTest(ImGuiKey_CapsLock));
static_assert(EnumKeynameTest(ImGuiKey_GamepadStart));
static_assert(EnumKeynameTest(ImGuiKey_GamepadLStickUp));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModCtrl));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModShift));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModAlt));
static_assert(EnumKeynameTest(ImGuiKey_ReservedForModSuper));
static_assert(EnumKeynameTest(ImGuiKey_GamepadStart));
static_assert(EnumKeynameTest(ImGuiKey_GamepadR3));
static_assert(EnumKeynameTest(ImGuiKey_GamepadLStickUp));
static_assert(EnumKeynameTest(ImGuiKey_GamepadRStickRight));
// Save every keydown status to out bitmask
uint64_t valueMask(0);
for (uint32_t i(0); i < ImGuiKey::ImGuiKey_NamedKey_COUNT; ++i) {
valueMask |=
ImGui::IsKeyDown(static_cast<ImGuiKey>(ImGuiKey_NamedKey_BEGIN + i))
? 0x0000000000000001ull << (i % 64)
: 0;
if (((i % 64) == 63) || i == (ImGuiKey::ImGuiKey_NamedKey_COUNT - 1)) {
pNewInput->mInputDownMask[i / 64] = valueMask;
valueMask = 0;
}
}
// Save analog keys (gamepad)
for (uint32_t i(0); i < NetImgui::Internal::CmdInput::kAnalog_Count; ++i) {
pNewInput->mInputAnalog[i] =
ImGui::GetIO()
.KeysData[NetImgui::Internal::CmdInput::kAnalog_First + i]
.AnalogValue;
}
}
// Copy waiting characters inputs
size_t addedKeyCount =
std::min<size_t>(NetImgui::Internal::ArrayCount(pNewInput->mKeyChars) -
pNewInput->mKeyCharCount,
mPendingInputChars.size());
if (addedKeyCount) {
memcpy(&pNewInput->mKeyChars[pNewInput->mKeyCharCount],
&mPendingInputChars[0], addedKeyCount * sizeof(ImWchar));
pNewInput->mKeyCharCount += static_cast<uint16_t>(addedKeyCount);
size_t charRemainCount = mPendingInputChars.size() - addedKeyCount;
if (charRemainCount > 0) {
memcpy(&mPendingInputChars[0], &mPendingInputChars[addedKeyCount],
mPendingInputChars.size() - addedKeyCount);
}
mPendingInputChars.resize(charRemainCount);
}
mPendingInputOut.Assign(pNewInput);
mLastUpdateTime = std::chrono::steady_clock::now();
}
} // namespace RemoteClient
} // namespace NetImguiServer
@@ -0,0 +1,199 @@
#pragma once
#include <Private/NetImgui_CmdPackets.h>
#include <chrono>
#include <unordered_map>
#include <vector>
#include "NetImguiServer_App.h"
namespace NetImguiServer {
namespace RemoteClient {
//=================================================================================================
// ImDrawData wrapper
//
// Allocate a single ImDrawList and assign it to itself.
//
// This child class leave the original ImDrawData behavior intact, but add the
// proper memory freeing of the ImDrawList member.
//=================================================================================================
struct NetImguiImDrawData : ImDrawData {
NetImguiImDrawData();
ImDrawList mCommandList;
uint64_t mFrameIndex = 0;
};
//=================================================================================================
// All info needed by the server to communicate with a remote client, and render
// its content
//=================================================================================================
struct Client {
static constexpr uint32_t kInvalidClient = static_cast<uint32_t>(-1);
using ExchPtrInput =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdInput>;
using ExchPtrClipboard =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdClipboard>;
using ExchPtrBackground =
NetImgui::Internal::ExchangePtr<NetImgui::Internal::CmdBackground>;
using ExchPtrImguiDraw = NetImgui::Internal::ExchangePtr<NetImguiImDrawData>;
using TextureTable = std::unordered_map<uint64_t, App::ServerTexture*>;
struct TexUpdateInfo {
uint64_t ClientId;
uint64_t Frame;
uint32_t UpdateId;
uint32_t Format;
uint32_t x;
uint32_t y;
uint32_t w;
uint32_t h;
uint32_t isCreate : 1;
uint32_t isDestroy : 1;
uint32_t isUpdate : 1;
uint32_t isDearImguiManaged : 1;
};
Client();
~Client();
Client(const Client&) = delete;
Client(const Client&&) = delete;
void operator=(const Client&) = delete;
void Initialize();
void Uninitialize();
void Release();
bool IsValid() const;
void ReceiveTexture(NetImgui::Internal::CmdTexture*);
void ReceiveDrawFrame(NetImgui::Internal::CmdDrawFrame*);
void ProcessCmdDrawFrame(NetImgui::Internal::CmdDrawFrame* pCmdDrawFrame);
NetImguiImDrawData* GetImguiDrawData(
ImTextureID EmtpyTextureID); // Get current active Imgui draw data
void CaptureImguiInput();
NetImgui::Internal::CmdInput* TakePendingInput();
NetImgui::Internal::CmdClipboard* TakePendingClipboard();
void ProcessPendingTextureCmds();
static bool Startup(uint32_t clientCountMax);
static void Shutdown();
static uint32_t GetCountMax();
static uint32_t GetFreeIndex();
static Client& Get(uint32_t index);
void* mpHAL_AreaRT = nullptr;
ImTextureData mHAL_AreaTexture;
uint16_t mAreaRTSizeX = 0; //!< Currently allocated RenderTarget size
uint16_t mAreaRTSizeY = 0; //!< Currently allocated RenderTarget size
uint16_t mAreaSizeX = 0; //!< Available area size available to remote client
uint16_t mAreaSizeY = 0; //!< Available area size available to remote client
char mInfoName[128] = {};
char mWindowID[128 + 16] = {};
char mInfoImguiVerName[16] = {};
char mInfoNetImguiVerName[16] = {};
uint32_t mInfoImguiVerID = 0;
uint32_t mInfoNetImguiVerID = 0;
char mConnectHost[64] = {}; //!< Connected Hostname of this remote client
int mConnectPort = 0; //!< Connected Port of this remote client
NetImguiImDrawData* mpImguiDrawData =
nullptr; //!< Current Imgui Data that this client is the owner of
NetImguiImDrawData* mpPendingDrawData =
nullptr; //!< Pending Imgui Data that has to have 1 frame display delay,
//!< to avoid issue with textures with pending updates
NetImgui::Internal::CmdDrawFrame* mpFrameDrawPrev =
nullptr; //!< Last valid DrawDrame (used by com thread, to uncompress
//!< data)
TextureTable mTextureTable; //!< Table matching client TextureUserID to
//!< textures allocated on Server for it
ExchPtrImguiDraw
mPendingImguiDrawDataIn; //!< Pending received Imgui DrawData, waiting to
//!< be taken ownership of
ExchPtrBackground mPendingBackgroundIn; //!< Background settings received and
//!< waiting to update client setting
ExchPtrClipboard mPendingClipboardIn; //!< Clipboard received from Client and
//!< waiting to be processed on Server
ExchPtrInput
mPendingInputOut; //!< Input command waiting to be sent out to client
ExchPtrClipboard mPendingClipboardOut; //!< Clipboard command waiting to be
//!< sent out to client
std::vector<ImWchar>
mPendingInputChars; //!< Captured Imgui characters input waiting to be
//!< added to new InputCmd
NetImgui::Internal::CmdTexture* mpPendingTextures[1024] =
{}; //!< Textures commands waiting to be processed in main update loop
std::atomic_uint64_t mPendingTextureReadIndex;
std::atomic_uint64_t mPendingTextureWriteIndex;
bool mbIsVisible = false; //!< If currently shown
bool mbIsActive = false; //!< Is the current active window (will receive
//!< input, only one is true at a time)
bool mbIsReleased = false; //!< If released in com thread and main thread
//!< should delete resources
bool mbIsConnected =
false; //!< If connected to a remote client. Set to false in Unitialize,
//!< after mIsRelease is set to unload resources
std::atomic_bool
mbIsFree; //!< If available to use for a new connected client
std::atomic_bool
mbCompressionSkipOncePending; //!< When we detect invalid previous
//!< DrawFrame command, cancel compression
//!< for 1 frame, to get good data
std::atomic_bool mbDisconnectPending; //!< Terminate Client/Server coms
std::chrono::steady_clock::time_point
mConnectedTime; //!< When the connection was established with this remote
//!< client
std::chrono::steady_clock::time_point
mLastUpdateTime; //!< When the client last send a content refresh request
std::chrono::steady_clock::time_point
mLastDrawFrame; //!< When we last receive a new drawframe commant
std::chrono::steady_clock::time_point
mLastIncomingComTime; //!< When we last received a valid command from
//!< client (to detect timeout)
uint32_t mClientConfigID =
0; //!< ID of ClientConfig that connected (if connection came from our
//!< list of ClientConfigs)
uint32_t mClientIndex = 0; //!< Entry idx into table of connected clients
uint64_t mStatsDataRcvd =
0; //!< Current amount of Bytes received since connected
uint64_t mStatsDataSent =
0; //!< Current amount of Bytes sent to client since connected
uint64_t mStatsDataRcvdPrev =
0; //!< Last amount of Bytes received since connected
uint64_t mStatsDataSentPrev =
0; //!< Last amount of Bytes sent to client since connected
std::chrono::steady_clock::time_point
mStatsTime; //!< Time when info was collected (with history of last x
//!< values)
uint32_t mStatsRcvdBps = 0; //!< Average Bytes received per second
uint32_t mStatsSentBps = 0; //!< Average Bytes sent per second
float mStatsDrawElapsedMs =
0.f; //!< Average milliseconds between 2 draw requests
uint32_t mStatsIndex = 0;
float mMousePos[2] = {0, 0};
float mMouseWheelPos[2] = {0, 0};
ImGuiMouseCursor mMouseCursor =
ImGuiMouseCursor_None; // Last mosue cursor remote client requested
ImGuiContext* mpBGContext =
nullptr; // Special Imgui Context used to render the background (only
// updated when needed)
bool mBGNeedUpdate = true; // Let engine know that we should regenerate the
// background draw commands
NetImgui::Internal::Network::SocketInfo* mpSocket =
nullptr; //!< Socket used for communications
NetImgui::Internal::CmdBackground
mBGSettings; //!< Settings for client background drawing settings
NetImgui::Internal::CmdPendingRead
mCmdPendingRead; //!< Used to get info on the next incoming command from
//!< Client
NetImgui::Internal::PendingCom
mPendingRcv; //!< Data being currently received from Client
NetImgui::Internal::PendingCom
mPendingSend; //!< Data being currently sent to Client
TexUpdateInfo mTextureHistory[256] =
{}; //!< Keeps track of texture changes (for debug info)
uint32_t mTextureHistoryIndex = 0;
uint64_t mLastDrawFrameIndex =
0; //!< Last frame index of valid drawdata drawn
};
} // namespace RemoteClient
} // namespace NetImguiServer
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
// Google modifications:
// - Added #include <cstdint> for uint32_t (not implicitly available in Google)
#pragma once
#include <cstdint>
#include <imgui.h>
namespace NetImguiServer {
namespace App {
struct ServerTexture;
}
} // namespace NetImguiServer
namespace NetImguiServer {
namespace UI {
constexpr uint32_t kWindowDPIDefault = 96;
bool Startup();
void Shutdown();
ImVec4 DrawImguiContent();
void DrawCenteredBackground(const App::ServerTexture* Texture,
const ImVec4& tint = ImVec4(1.f, 1.f, 1.f, 1.f));
float GetDisplayFPS();
const App::ServerTexture* GetBackgroundTexture();
} // namespace UI
} // namespace NetImguiServer
+240
View File
@@ -0,0 +1,240 @@
MIT License
Copyright (c) 2021 Sammy Fatnassi (Github: @Sammyfreg)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=============
Code in the google subdirectory is licensed under the Apache License:
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Copyright 2026 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,324 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// WASM networking backend for NetImgui.
//
// Implements the NetImgui::Internal::Network interface using Emscripten
// WebSockets. This file is compiled only under the Emscripten toolchain and
// provides the browser-side transport layer for netimgui draw data.
#include "NetImgui_Shared.h"
#ifndef __EMSCRIPTEN__
#error "This file must be compiled with emscripten."
#endif
#include <emscripten/console.h>
#include <emscripten/websocket.h>
#include <algorithm>
#include <atomic>
#include <cstring>
#include <mutex>
#include <vector>
#include "NetImgui_CmdPackets.h"
#include "google/logging.h"
#include "google/network_status.h"
namespace NetImgui {
namespace Internal {
namespace Network {
struct SocketInfo {
EMSCRIPTEN_WEBSOCKET_T mSocket = 0;
std::atomic<bool> mConnected{false};
std::atomic<bool> mError{false};
std::atomic<bool> mClosed{false};
std::atomic<int> mCloseCode{0};
std::vector<uint8_t> mBuffer;
std::mutex mBufferMutex;
int mSendSizeMax =
1024 * 1024; // Interface compatibility with other backends
};
// --- WebSocket event callbacks ---
static EM_BOOL OnWebSocketOpen(int /*event_type*/,
const EmscriptenWebSocketOpenEvent* /*event*/,
void* user_data) {
auto* socket = static_cast<SocketInfo*>(user_data);
if (socket) {
socket->mConnected = true;
}
return EM_TRUE;
}
static EM_BOOL OnWebSocketMessage(int /*event_type*/,
const EmscriptenWebSocketMessageEvent* event,
void* user_data) {
auto* socket = static_cast<SocketInfo*>(user_data);
if (socket && !event->isText) {
std::lock_guard<std::mutex> lock(socket->mBufferMutex);
size_t old_size = socket->mBuffer.size();
socket->mBuffer.insert(socket->mBuffer.end(), event->data,
event->data + event->numBytes);
static int msg_count = 0;
++msg_count;
VLOG(1, "onmessage #%d: %d bytes, buffer: %zu -> %zu", msg_count,
event->numBytes, old_size, socket->mBuffer.size());
} else if (socket && event->isText) {
VLOG(1, "onmessage: TEXT frame (%d bytes), IGNORED", event->numBytes);
}
return EM_TRUE;
}
static EM_BOOL OnWebSocketClose(int /*event_type*/,
const EmscriptenWebSocketCloseEvent* event,
void* user_data) {
auto* socket = static_cast<SocketInfo*>(user_data);
if (socket) {
socket->mClosed = true;
socket->mCloseCode = event->code;
}
return EM_TRUE;
}
static EM_BOOL OnWebSocketError(int /*event_type*/,
const EmscriptenWebSocketErrorEvent* /*event*/,
void* user_data) {
auto* socket = static_cast<SocketInfo*>(user_data);
if (socket) {
socket->mError = true;
}
return EM_TRUE;
}
// --- Network interface implementation ---
bool Startup() { return emscripten_websocket_is_supported(); }
void Shutdown() {}
SocketInfo* Connect(const char* server_host, uint32_t /*server_port*/) {
if (!emscripten_websocket_is_supported()) return nullptr;
SocketInfo* socket_info = netImguiNew<SocketInfo>();
EmscriptenWebSocketCreateAttributes attr;
emscripten_websocket_init_create_attributes(&attr);
// The web viewer always passes a complete WebSocket URL (e.g.
// "ws://host:8080/ui", built from the page origin by WsUrl), so it is used
// verbatim and server_port is ignored — a browser reaches the viewer's
// paths on the page's own shared port, not a dedicated NetImgui port.
const std::string url(server_host);
attr.url = url.c_str();
attr.createOnMainThread = EM_TRUE;
LOG(Info, "Connecting WebSocket to: %s", url.c_str());
socket_info->mSocket = emscripten_websocket_new(&attr);
if (socket_info->mSocket <= 0) {
netImguiDelete(socket_info);
return nullptr;
}
emscripten_websocket_set_onopen_callback(socket_info->mSocket, socket_info,
OnWebSocketOpen);
emscripten_websocket_set_onmessage_callback(socket_info->mSocket, socket_info,
OnWebSocketMessage);
emscripten_websocket_set_onclose_callback(socket_info->mSocket, socket_info,
OnWebSocketClose);
emscripten_websocket_set_onerror_callback(socket_info->mSocket, socket_info,
OnWebSocketError);
return socket_info;
}
// Abandoned sockets are not freed immediately: with -pthread, websocket
// events are queued across threads, so an already-queued close/error event
// can still dereference the SocketInfo after emscripten_websocket_delete().
// When the freed block was recycled for the next socket, such a late event
// stamped a stale mClosed flag onto a healthy connection, which the
// reconnect logic then tore down — a self-sustaining reconnect loop. Keep
// abandoned sockets in a small ring and free them several disconnects
// later, when any queued events are long gone.
static SocketInfo* s_socket_graveyard[8] = {};
static int s_socket_graveyard_idx = 0;
void Disconnect(SocketInfo* client_socket) {
if (!client_socket) return;
client_socket->mClosed = true;
// Detach this socket from future events; queued events may already hold
// the pointer (the graveyard above covers those).
emscripten_websocket_set_onopen_callback(client_socket->mSocket, nullptr,
OnWebSocketOpen);
emscripten_websocket_set_onmessage_callback(client_socket->mSocket, nullptr,
OnWebSocketMessage);
emscripten_websocket_set_onclose_callback(client_socket->mSocket, nullptr,
OnWebSocketClose);
emscripten_websocket_set_onerror_callback(client_socket->mSocket, nullptr,
OnWebSocketError);
emscripten_websocket_close(client_socket->mSocket, 1000,
"Normal Disconnection");
emscripten_websocket_delete(client_socket->mSocket);
{
// Release the receive buffer now; only the flags must stay valid.
std::lock_guard<std::mutex> lock(client_socket->mBufferMutex);
client_socket->mBuffer.clear();
client_socket->mBuffer.shrink_to_fit();
}
if (s_socket_graveyard[s_socket_graveyard_idx]) {
netImguiDelete(s_socket_graveyard[s_socket_graveyard_idx]);
}
s_socket_graveyard[s_socket_graveyard_idx] = client_socket;
s_socket_graveyard_idx = (s_socket_graveyard_idx + 1) % 8;
}
bool DataReceivePending(SocketInfo* client_socket) {
if (!client_socket) return false;
if (client_socket->mError || client_socket->mClosed) {
// Connection is dead — flush any buffered data so we stop processing
// stale commands that arrived before the close.
std::lock_guard<std::mutex> lock(client_socket->mBufferMutex);
if (!client_socket->mBuffer.empty()) {
LOG(Warning, "Connection closed/error. Discarding %zu buffered bytes.",
client_socket->mBuffer.size());
client_socket->mBuffer.clear();
}
return false;
}
std::lock_guard<std::mutex> lock(client_socket->mBufferMutex);
return !client_socket->mBuffer.empty();
}
void DataReceive(SocketInfo* client_socket, PendingCom& pending_rcv) {
if (!client_socket || !pending_rcv.pCommand) {
pending_rcv.bError = true;
return;
}
if (!client_socket->mConnected) {
pending_rcv.bError = false; // Not ready yet, caller will retry.
return;
}
// The size field comes off the wire; a value smaller than what has already
// been read (e.g. a command header claiming < 8 bytes, from a corrupted or
// desynced stream) would underflow the subtraction below into a huge
// size_t and memcpy past the destination command buffer.
if (pending_rcv.pCommand->mSize < pending_rcv.SizeCurrent) {
LOG(Error, "DataReceive: wire size %u < %zu bytes already read; stream "
"is corrupt",
pending_rcv.pCommand->mSize,
static_cast<size_t>(pending_rcv.SizeCurrent));
pending_rcv.bError = true;
return;
}
size_t bytes_to_read = pending_rcv.pCommand->mSize - pending_rcv.SizeCurrent;
if (bytes_to_read == 0) return;
std::lock_guard<std::mutex> lock(client_socket->mBufferMutex);
VLOG(1, "DataReceive: want=%zu, have=%zu, cmd_size=%u, progress=%zu",
bytes_to_read, client_socket->mBuffer.size(),
pending_rcv.pCommand->mSize, pending_rcv.SizeCurrent);
if (client_socket->mBuffer.empty()) {
if (client_socket->mError || client_socket->mClosed) {
pending_rcv.bError = true;
}
return;
}
size_t bytes_to_consume =
std::min(bytes_to_read, client_socket->mBuffer.size());
if (bytes_to_consume > 0) {
memcpy(reinterpret_cast<uint8_t*>(pending_rcv.pCommand) +
pending_rcv.SizeCurrent,
client_socket->mBuffer.data(), bytes_to_consume);
client_socket->mBuffer.erase(
client_socket->mBuffer.begin(),
client_socket->mBuffer.begin() + bytes_to_consume);
pending_rcv.SizeCurrent += bytes_to_consume;
pending_rcv.bError = false;
}
}
void DataSend(SocketInfo* client_socket, PendingCom& pending_send) {
if (!client_socket || client_socket->mClosed || client_socket->mError ||
!pending_send.pCommand) {
pending_send.bError = true;
return;
}
if (!client_socket->mConnected) {
pending_send.bError = false; // Not ready yet, caller will retry.
return;
}
size_t bytes_remaining =
pending_send.pCommand->mSize - pending_send.SizeCurrent;
if (bytes_remaining == 0) return;
EMSCRIPTEN_RESULT result = emscripten_websocket_send_binary(
client_socket->mSocket,
reinterpret_cast<uint8_t*>(pending_send.pCommand) +
pending_send.SizeCurrent,
bytes_remaining);
if (result == EMSCRIPTEN_RESULT_SUCCESS) {
pending_send.SizeCurrent += bytes_remaining;
pending_send.bError = false;
} else {
pending_send.bError = true;
}
}
SocketInfo* ListenStart(uint32_t /*listen_port*/) {
return nullptr; // Browsers cannot open listening ports.
}
SocketInfo* ListenConnect(SocketInfo* /*listen_socket*/) { return nullptr; }
int GetCloseCode(SocketInfo* client_socket) {
return client_socket ? client_socket->mCloseCode.load() : 0;
}
ReadyState GetReadyState(SocketInfo* client_socket) {
if (!client_socket) return ReadyState::kDisconnected;
if (client_socket->mError) return ReadyState::kError;
if (client_socket->mClosed) return ReadyState::kClosed;
uint16_t ready_state = 0;
emscripten_websocket_get_ready_state(client_socket->mSocket, &ready_state);
switch (ready_state) {
case 0:
return ReadyState::kConnecting;
case 1:
return ReadyState::kOpen;
case 2:
return ReadyState::kClosing;
case 3:
return ReadyState::kClosed;
}
return ReadyState::kError;
}
} // namespace Network
} // namespace Internal
} // namespace NetImgui
@@ -0,0 +1,100 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NETIMGUI_GOOGLE_LOGGING_H_
#define THIRD_PARTY_NETIMGUI_GOOGLE_LOGGING_H_
#include <time.h>
#include <chrono>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#if defined(__EMSCRIPTEN__)
#include <emscripten/console.h>
#endif
namespace NetImgui {
enum class LogSeverity { kInfo, kWarning, kError };
// Set to 1 or higher to enable VLOG messages at runtime.
constexpr int kLogVerbosity = 0;
__attribute__((format(printf, 4, 5))) inline void NetImGuiLog(
LogSeverity severity, const char* file, int line, const char* fmt, ...) {
char buf[1024];
const char* basename = strrchr(file, '/');
basename = basename ? basename + 1 : file;
auto now_tp = std::chrono::system_clock::now();
time_t now = std::chrono::system_clock::to_time_t(now_tp);
auto now_usec = std::chrono::duration_cast<std::chrono::microseconds>(
now_tp.time_since_epoch())
.count() %
1000000;
struct tm tm_info;
localtime_r(&now, &tm_info);
const char severity_char = severity == LogSeverity::kError ? 'E'
: severity == LogSeverity::kWarning ? 'W'
: 'I';
int prefix_len = snprintf(
buf, sizeof(buf), "%c%02d%02d %02d:%02d:%02d.%06d %s:%d] ", severity_char,
tm_info.tm_mon + 1, tm_info.tm_mday, tm_info.tm_hour, tm_info.tm_min,
tm_info.tm_sec, static_cast<int>(now_usec), basename, line);
if (prefix_len < 0 || prefix_len >= static_cast<int>(sizeof(buf))) {
prefix_len = 0; // overwrite prefix on error or truncation
}
va_list args;
va_start(args, fmt);
vsnprintf(buf + prefix_len, sizeof(buf) - prefix_len, fmt, args);
va_end(args);
if (severity == LogSeverity::kInfo) {
#if defined(__EMSCRIPTEN__)
emscripten_out(buf);
#else
fputs(buf, stdout);
fputc('\n', stdout);
fflush(stdout);
#endif
} else {
#if defined(__EMSCRIPTEN__)
emscripten_err(buf);
#else
fputs(buf, stderr);
fputc('\n', stderr);
fflush(stderr);
#endif
}
}
} // namespace NetImgui
#define LOG(severity, fmt, ...) \
::NetImgui::NetImGuiLog(::NetImgui::LogSeverity::k##severity, __FILE__, \
__LINE__, fmt, ##__VA_ARGS__)
#define VLOG(level, fmt, ...) \
do { \
if (::NetImgui::kLogVerbosity >= (level)) \
::NetImgui::NetImGuiLog(::NetImgui::LogSeverity::kInfo, __FILE__, \
__LINE__, fmt, ##__VA_ARGS__); \
} while (0)
#endif // THIRD_PARTY_NETIMGUI_GOOGLE_LOGGING_H_
@@ -0,0 +1,75 @@
// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Connection-state query for the WASM WebSocket network backend.
//
// Stock NetImgui has no such query and does not need one: its Connect()
// implementations block until the connection is established, so a non-null
// SocketInfo is always usable and later failures surface as DataSend /
// DataReceive errors inside the client thread. In the browser,
// emscripten_websocket_new() returns a socket handle immediately while the
// connection completes (or fails) asynchronously, and no client state
// machine is watching it. Callers poll this state to hold back traffic
// until the socket is actually open and to detect closure for reconnecting.
#ifndef THIRD_PARTY_NETIMGUI_GOOGLE_NETWORK_STATUS_H_
#define THIRD_PARTY_NETIMGUI_GOOGLE_NETWORK_STATUS_H_
namespace NetImgui {
namespace Internal {
namespace Network {
struct SocketInfo;
enum class ReadyState {
kDisconnected, // Null socket.
kConnecting,
kOpen,
kClosing,
kClosed,
kError,
};
// Implemented in NetImgui_NetworkWASM.cpp (Emscripten builds only).
ReadyState GetReadyState(SocketInfo* client_socket);
// The WebSocket close code once the socket has closed, else 0. Lets callers
// distinguish a deliberate server-side rejection (e.g. 4001 = driver slot
// taken) from an ordinary drop. Implemented in NetImgui_NetworkWASM.cpp.
int GetCloseCode(SocketInfo* client_socket);
// Human-readable state name, for status overlays and logs.
inline const char* ReadyStateName(ReadyState state) {
switch (state) {
case ReadyState::kDisconnected:
return "Disconnected";
case ReadyState::kConnecting:
return "Connecting";
case ReadyState::kOpen:
return "Open";
case ReadyState::kClosing:
return "Closing";
case ReadyState::kClosed:
return "Closed";
case ReadyState::kError:
return "Error";
}
return "Unknown";
}
} // namespace Network
} // namespace Internal
} // namespace NetImgui
#endif // THIRD_PARTY_NETIMGUI_GOOGLE_NETWORK_STATUS_H_