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