Initial integration of NetImgui. The engine/netimgui window can be used to connect to the NetImgui server.

This commit is contained in:
Arnaud Jamin
2025-01-01 15:46:19 -05:00
parent b07213cd0f
commit 123ae5b68f
29 changed files with 4396 additions and 9 deletions
@@ -0,0 +1,126 @@
#include "CogEngineWindow_NetImGui.h"
#include "CogWindowWidgets.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "NetImgui_Api.h"
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_NetImGui::Initialize()
{
Super::Initialize();
bHasMenu = true;
Config = GetConfig<UCogEngineConfig_NetImGui>();
}
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_NetImGui::Shutdown()
{
}
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_NetImGui::ResetConfig()
{
Super::ResetConfig();
Config->Reset();
}
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_NetImGui::RenderHelp()
{
ImGui::Text("");
}
//--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_NetImGui::RenderContent()
{
Super::RenderContent();
#if NETIMGUI_ENABLED
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Settings"))
{
{
static char Buffer[256] = "";
ImStrncpy(Buffer, TCHAR_TO_ANSI(*Config->ServerName), IM_ARRAYSIZE(Buffer));
if (ImGui::InputText("Server Name", Buffer, IM_ARRAYSIZE(Buffer)))
{
Config->ServerName = FString(Buffer);
}
}
ImGui::InputInt("Server Port", &Config->ServerPort);
{
static char Buffer[256] = "";
ImStrncpy(Buffer, TCHAR_TO_ANSI(*Config->ClientName), IM_ARRAYSIZE(Buffer));
if (ImGui::InputText("Client Name", Buffer, IM_ARRAYSIZE(Buffer)))
{
Config->ClientName = FString(Buffer);
}
}
ImGui::InputInt("Client Port", &Config->ClientPort);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
if (NetImgui::IsConnected())
{
ImGui::TextUnformatted("Status: Connected");
if (ImGui::Button("Disconnect"))
{
NetImgui::Disconnect();
}
}
else if (NetImgui::IsConnectionPending())
{
ImGui::TextUnformatted("Status: Waiting Server");
if (ImGui::Button("Cancel"))
{
NetImgui::Disconnect();
}
}
else // No connection
{
ImGui::TextUnformatted("Status: Not Connected");
if (ImGui::Button("Connect", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
{
const auto clientName = StringCast<ANSICHAR>(*Config->ClientName);
const auto serverName = StringCast<ANSICHAR>(*Config->ServerName);
NetImgui::ConnectToApp(clientName.Get(), serverName.Get(), Config->ServerPort, nullptr, nullptr);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Attempt a connection to a remote netImgui server at the provided address.");
}
if (ImGui::Button("Listen", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
{
const auto clientName = StringCast<ANSICHAR>(*Config->ClientName);
NetImgui::ConnectFromApp(clientName.Get(), Config->ClientPort, nullptr, nullptr);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Start listening for a connection request by a remote netImgui server, on the provided Port.");
}
}
#endif // #if NETIMGUI_ENABLED
}
void UCogEngineConfig_NetImGui::Reset()
{
Super::Reset();
#if NETIMGUI_ENABLED
ClientName = FString("cog");
ServerName = FString("localhost");
ClientPort = NetImgui::kDefaultClientPort;
ServerPort = NetImgui::kDefaultServerPort;
#endif
}
@@ -0,0 +1,54 @@
#pragma once
#include "CoreMinimal.h"
#include "CogCommonConfig.h"
#include "CogWindow.h"
#include "CogEngineWindow_NetImGui.generated.h"
class UCogEngineConfig_NetImGui;
class COGENGINE_API FCogEngineWindow_NetImGui : public FCogWindow
{
typedef FCogWindow Super;
public:
virtual void Initialize() override;
virtual void Shutdown() override;
protected:
virtual void ResetConfig() override;
virtual void RenderHelp() override;
virtual void RenderContent() override;
private:
TWeakObjectPtr<UCogEngineConfig_NetImGui> Config = nullptr;
};
//--------------------------------------------------------------------------------------------------------------------------
UCLASS(Config = Cog)
class UCogEngineConfig_NetImGui : public UCogCommonConfig
{
GENERATED_BODY()
public:
virtual void Reset() override;
UPROPERTY(Config)
FString ClientName = FString("cog");
UPROPERTY(Config)
FString ServerName = FString("localhost");
UPROPERTY(Config)
int32 ServerPort = 8888;
UPROPERTY(Config)
int32 ClientPort = 8889;
};
@@ -12,6 +12,7 @@ public class CogImgui : ModuleRules
"Core",
"ImGui",
"ImPlot",
"NetImgui",
});
PrivateDependencyModuleNames.AddRange(new[]
@@ -21,7 +22,8 @@ public class CogImgui : ModuleRules
"Engine",
"InputCore",
"Slate",
"SlateCore"
"SlateCore",
"Sockets"
});
if (Target.bBuildEditor)
@@ -1,12 +1,22 @@
#include "CogImguiConfig.h"
THIRD_PARTY_INCLUDES_START
#include <imgui.cpp>
#include <imgui_demo.cpp>
#include <imgui_draw.cpp>
#include <imgui_tables.cpp>
#include <imgui_widgets.cpp>
#include <implot.cpp>
#include <implot_demo.cpp>
#include <implot_items.cpp>
#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"
THIRD_PARTY_INCLUDES_END
@@ -3,10 +3,10 @@
#include "CogDebugDrawImGui.h"
#include "CogImguiHelper.h"
#include "CogImguiInputHelper.h"
#include "CogWindowConsoleCommandManager.h"
#include "CogWindow_Layouts.h"
#include "CogWindow_Settings.h"
#include "CogWindow_Spacing.h"
#include "CogWindowConsoleCommandManager.h"
#include "CogWindowHelper.h"
#include "CogWindowWidgets.h"
#include "Engine/Engine.h"
@@ -14,6 +14,7 @@
#include "HAL/IConsoleManager.h"
#include "imgui_internal.h"
#include "Misc/EngineVersionComparison.h"
#include "NetImgui_Api.h"
FString UCogWindowManager::ToggleInputCommand = TEXT("Cog.ToggleInput");
FString UCogWindowManager::DisableInputCommand = TEXT("Cog.DisableInput");
@@ -21,6 +22,8 @@ FString UCogWindowManager::LoadLayoutCommand = TEXT("Cog.LoadLayout");
FString UCogWindowManager::SaveLayoutCommand = TEXT("Cog.SaveLayout");
FString UCogWindowManager::ResetLayoutCommand = TEXT("Cog.ResetLayout");
bool UCogWindowManager::IsNetImguiInitialized = false;
//--------------------------------------------------------------------------------------------------------------------------
UCogWindowManager::UCogWindowManager()
{
@@ -128,7 +131,17 @@ void UCogWindowManager::InitializeInternal()
}
}));
#if NETIMGUI_ENABLED
if (IsNetImguiInitialized == false)
{
NetImgui::Startup();
IsNetImguiInitialized = true;
}
#endif
IsInitialized = true;
}
//--------------------------------------------------------------------------------------------------------------------------
@@ -136,19 +149,30 @@ void UCogWindowManager::Shutdown()
{
FCogImGuiContextScope ImGuiContextScope(Context);
//------------------------------------------------------------
//------------------------------------------------------------------
// Call PreSaveConfig before destroying imgui context
// if PreSaveConfig needs to read ImGui IO for example
//------------------------------------------------------------
//------------------------------------------------------------------
for (FCogWindow* Window : Windows)
{
Window->PreSaveConfig();
}
//------------------------------------------------------------
//------------------------------------------------------------------
// NetImgui must be shutdown before imgui as it uses context hooks
//------------------------------------------------------------------
#if NETIMGUI_ENABLED
if (IsNetImguiInitialized)
{
NetImgui::Shutdown();
IsNetImguiInitialized = false;
}
#endif
//------------------------------------------------------------------
// Destroy ImGui before destroying the windows to make sure
// imgui serialize their visibility state in imgui.ini
//------------------------------------------------------------
//------------------------------------------------------------------
if (IsInitialized == true)
{
Context.Shutdown();
@@ -159,6 +159,8 @@ protected:
bool bHideAllWindows = false;
bool IsInitialized = false;
static bool IsNetImguiInitialized;
};
//--------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,11 @@
using UnrealBuildTool;
using System.IO;
public class NetImgui : ModuleRules
{
public NetImgui(ReadOnlyTargetRules Target) : base(Target)
{
Type = ModuleType.External;
PublicSystemIncludePaths.Add(ModuleDirectory);
}
}
+285
View File
@@ -0,0 +1,285 @@
#pragma once
//=================================================================================================
//! @Name : NetImgui
//=================================================================================================
//! @author : Sammy Fatnassi
//! @date : 2024/12/10
//! @version : v1.12.1
//! @Details : For integration info : https://github.com/sammyfreg/netImgui/wiki
//=================================================================================================
#define NETIMGUI_VERSION "1.12.1" // Fixed disconnect thread contention and clipboard command
#define NETIMGUI_VERSION_NUM 11201
//-------------------------------------------------------------------------------------------------
// 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
#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 {
kTexFmtA8,
kTexFmtRGBA8,
// 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.
//=================================================================================================
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
//=================================================================================================
NETIMGUI_API void SendDataTexture(ImTextureID textureId, void* pData, uint16_t width, uint16_t height, eTexFormat format, uint32_t dataSize=0);
//=================================================================================================
// 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);
//=================================================================================================
// 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);
}
//=================================================================================================
// 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,62 @@
#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 "Private/NetImgui_WarningDisableImgui.h" // Disable some extra warning generated by imgui_internal in '-Wall'
#include <imgui_internal.h> // Only needed when compiling NetImgui, not when using the NetImgui Api
#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,795 @@
#include "NetImgui_Shared.h"
#include "NetImgui_WarningDisable.h"
#if NETIMGUI_ENABLED
#include <algorithm>
#include <thread>
#include "NetImgui_Client.h"
#include "NetImgui_Network.h"
#include "NetImgui_CmdPackets.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);
client.mFontCreationFunction = FontCreateFunction;
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);
client.mFontCreationFunction = FontCreateFunction;
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.mSavedDisplaySize = ImGui::GetIO().DisplaySize;
client.mbValidDrawFrame = false;
// Take into account delay until next method call, for more precise fps
if( client.mDesiredFps > 0.f && (elapsedDrawMs + elapsedCheckMs/2.f) > (1000.f/client.mDesiredFps) )
{
client.mLastOutgoingDrawTime = std::chrono::steady_clock::now();
client.mbValidDrawFrame = true;
}
ProcessInputData(client);
// 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 != 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);
// 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;
// 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 'NewFrame' ImGui hook, in which case no need to call this again
if( !client.mbInsideHook ){
ImGui::Render();
}
// Prepare the Dear Imgui DrawData for later tranmission to Server
client.ProcessDrawData(ImGui::GetDrawData(), 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);
}
// Restore display size, so we never lose original setting that may get updated after initial connection
if( client.mbIsRemoteDrawing ) {
ImGui::GetIO().DisplaySize = client.mSavedDisplaySize;
}
}
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;
CmdTexture* pCmdTexture = nullptr;
// Makes sure even 32bits ImTextureID value are received properly as 64bits
uint64_t texId64(0);
static_assert(sizeof(uint64_t) >= sizeof(textureId), "ImTextureID is bigger than 64bits, CmdTexture::mTextureId needs to be updated to support it");
reinterpret_cast<ImTextureID*>(&texId64)[0] = textureId;
// Add/Update a texture
if( pData != nullptr )
{
if( format != eTexFormat::kTexFmtCustom ){
dataSize = GetTexture_BytePerImage(format, width, height);
}
uint32_t SizeNeeded = dataSize + sizeof(CmdTexture);
pCmdTexture = netImguiSizedNew<CmdTexture>(SizeNeeded);
pCmdTexture->mpTextureData.SetPtr(reinterpret_cast<uint8_t*>(&pCmdTexture[1]));
memcpy(pCmdTexture->mpTextureData.Get(), pData, dataSize);
pCmdTexture->mSize = SizeNeeded;
pCmdTexture->mWidth = width;
pCmdTexture->mHeight = height;
pCmdTexture->mTextureId = texId64;
pCmdTexture->mFormat = static_cast<uint8_t>(format);
pCmdTexture->mpTextureData.ToOffset();
// Detects when user is sending the font texture
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 = textureId;
}
}
// Texture to remove
else
{
pCmdTexture = netImguiNew<CmdTexture>();
pCmdTexture->mTextureId = texId64;
pCmdTexture->mWidth = 0;
pCmdTexture->mHeight = 0;
pCmdTexture->mFormat = eTexFormat::kTexFmt_Invalid;
pCmdTexture->mpTextureData.SetOff(0);
}
// In unlikely event of too many textures, wait for them to be processed
// (if connected) or Process them now (if not)
while( (client.mTexturesPendingCreated - client.mTexturesPendingSent) >= static_cast<uint32_t>(ArrayCount(client.mTexturesPending)) )
{
if( IsConnected() )
std::this_thread::yield();
else
client.ProcessTexturePending();
}
uint32_t idx = client.mTexturesPendingCreated.fetch_add(1) % static_cast<uint32_t>(ArrayCount(client.mTexturesPending));
client.mTexturesPending[idx] = pCmdTexture;
// If not connected to server yet, update all pending textures
if( !IsConnected() )
client.ProcessTexturePending();
}
//=================================================================================================
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;
}
//=================================================================================================
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]);
// 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)
{
if(abs(gpClientInfo->mFontCreationScaling - pCmdInput->mFontDPIScaling) > 0.01f)
{
gpClientInfo->mFontCreationFunction(gpClientInfo->mFontCreationScaling, pCmdInput->mFontDPIScaling);
gpClientInfo->mFontCreationScaling = 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;
}
#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"
@@ -0,0 +1,716 @@
#include "NetImgui_Shared.h"
#if NETIMGUI_ENABLED
#include "NetImgui_WarningDisable.h"
#include "NetImgui_Client.h"
#include "NetImgui_Network.h"
#include "NetImgui_CmdPackets.h"
namespace NetImgui { namespace Internal { namespace Client
{
//=================================================================================================
// SAVED IMGUI CONTEXT
// Because we overwrite some Imgui context IO values, we save them before makign any change
// and restore them after detecting a disconnection
//=================================================================================================
void SavedImguiContext::Save(ImGuiContext* copyFrom)
{
ScopedImguiContext scopedContext(copyFrom);
ImGuiIO& sourceIO = ImGui::GetIO();
mSavedContext = true;
mConfigFlags = sourceIO.ConfigFlags;
mBackendFlags = sourceIO.BackendFlags;
mBackendPlatformName = sourceIO.BackendPlatformName;
mBackendRendererName = sourceIO.BackendRendererName;
mDrawMouse = sourceIO.MouseDrawCursor;
mFontGlobalScale = sourceIO.FontGlobalScale;
mFontGeneratedSize = sourceIO.Fonts->Fonts.Size > 0 ? sourceIO.Fonts->Fonts[0]->FontSize : 13.f; // Save size to restore the font to original size
#if IMGUI_VERSION_NUM < 18700
memcpy(mKeyMap, sourceIO.KeyMap, sizeof(mKeyMap));
#endif
#if IMGUI_VERSION_NUM < 19110
mGetClipboardTextFn = sourceIO.GetClipboardTextFn;
mSetClipboardTextFn = sourceIO.SetClipboardTextFn;
mClipboardUserData = sourceIO.ClipboardUserData;
#else
ImGuiPlatformIO& plaformIO = ImGui::GetPlatformIO();
mGetClipboardTextFn = plaformIO.Platform_GetClipboardTextFn;
mSetClipboardTextFn = plaformIO.Platform_SetClipboardTextFn;
mClipboardUserData = plaformIO.Platform_ClipboardUserData;
#endif
}
void SavedImguiContext::Restore(ImGuiContext* copyTo)
{
ScopedImguiContext scopedContext(copyTo);
ImGuiIO& destIO = ImGui::GetIO();
mSavedContext = false;
destIO.ConfigFlags = mConfigFlags;
destIO.BackendFlags = mBackendFlags;
destIO.BackendPlatformName = mBackendPlatformName;
destIO.BackendRendererName = mBackendRendererName;
destIO.MouseDrawCursor = mDrawMouse;
destIO.FontGlobalScale = mFontGlobalScale;
#if IMGUI_VERSION_NUM < 18700
memcpy(destIO.KeyMap, mKeyMap, sizeof(destIO.KeyMap));
#endif
#if IMGUI_VERSION_NUM < 19110
destIO.GetClipboardTextFn = mGetClipboardTextFn;
destIO.SetClipboardTextFn = mSetClipboardTextFn;
destIO.ClipboardUserData = mClipboardUserData;
#else
ImGuiPlatformIO& plaformIO = ImGui::GetPlatformIO();
plaformIO.Platform_GetClipboardTextFn = mGetClipboardTextFn;
plaformIO.Platform_SetClipboardTextFn = mSetClipboardTextFn;
plaformIO.Platform_ClipboardUserData = mClipboardUserData;
#endif
}
//=================================================================================================
// GET CLIPBOARD
// Content received from the Server
//=================================================================================================
#if IMGUI_VERSION_NUM < 19110
static const char* GetClipboardTextFn_NetImguiImpl(void* user_data_ctx)
{
const ClientInfo* pClient = reinterpret_cast<const ClientInfo*>(user_data_ctx);
return pClient && pClient->mpCmdClipboard ? pClient->mpCmdClipboard->mContentUTF8.Get() : nullptr;
}
#else
static const char* GetClipboardTextFn_NetImguiImpl(ImGuiContext* ctx)
{
ScopedImguiContext scopedContext(ctx);
const ClientInfo* pClient = reinterpret_cast<const ClientInfo*>(ImGui::GetPlatformIO().Platform_ClipboardUserData);
return pClient && pClient->mpCmdClipboard ? pClient->mpCmdClipboard->mContentUTF8.Get() : nullptr;
}
#endif
//=================================================================================================
// SET CLIPBOARD
//=================================================================================================
#if IMGUI_VERSION_NUM < 19110
static void SetClipboardTextFn_NetImguiImpl(void* user_data_ctx, const char* text)
{
if(user_data_ctx){
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(user_data_ctx);
CmdClipboard* pClipboardOut = CmdClipboard::Create(text);
pClient->mPendingClipboardOut.Assign(pClipboardOut);
}
}
#else
static void SetClipboardTextFn_NetImguiImpl(ImGuiContext* ctx, const char* text)
{
ScopedImguiContext scopedContext(ctx);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(ImGui::GetPlatformIO().Platform_ClipboardUserData);
CmdClipboard* pClipboardOut = CmdClipboard::Create(text);
pClient->mPendingClipboardOut.Assign(pClipboardOut);
}
#endif
//=================================================================================================
// INCOM: INPUT
// Receive new keyboard/mouse/screen resolution input to pass on to dearImgui
//=================================================================================================
void Communications_Incoming_Input(ClientInfo& client)
{
auto pCmdInput = static_cast<CmdInput*>(client.mPendingRcv.pCommand);
client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
client.mDesiredFps = pCmdInput->mDesiredFps > 0.f ? pCmdInput->mDesiredFps : 0.f;
size_t keyCount(pCmdInput->mKeyCharCount);
client.mPendingKeyIn.AddData(pCmdInput->mKeyChars, keyCount);
client.mPendingInputIn.Assign(pCmdInput);
}
//=================================================================================================
// INCOM: CLIPBOARD
// Receive server new clipboard content, updating internal cache
//=================================================================================================
void Communications_Incoming_Clipboard(ClientInfo& client)
{
auto pCmdClipboard = static_cast<CmdClipboard*>(client.mPendingRcv.pCommand);
client.mPendingRcv.bAutoFree = false; // Taking ownership of the data
pCmdClipboard->ToPointers();
client.mPendingClipboardIn.Assign(pCmdClipboard);
}
//=================================================================================================
// OUTCOM: TEXTURE
// Transmit all pending new/updated texture
//=================================================================================================
void Communications_Outgoing_Textures(ClientInfo& client)
{
client.ProcessTexturePending();
if( client.mbHasTextureUpdate )
{
for(auto& cmdTexture : client.mTextures)
{
if( !cmdTexture.mbSent && cmdTexture.mpCmdTexture )
{
client.mPendingSend.pCommand = cmdTexture.mpCmdTexture;
client.mPendingSend.bAutoFree = cmdTexture.mpCmdTexture->mFormat == eTexFormat::kTexFmt_Invalid; // Texture as been marked for deletion, can now free memory allocated for this
cmdTexture.mbSent = true;
return; // Exit as soon as we find a texture to send, so thread can proceed with transmitting it
}
}
client.mbHasTextureUpdate = false; // No pending texture detected, mark as 'all sent'
}
}
//=================================================================================================
// OUTCOM: BACKGROUND
// Transmit the current client background settings
//=================================================================================================
void Communications_Outgoing_Background(ClientInfo& client)
{
CmdBackground* pPendingBackground = client.mPendingBackgroundOut.Release();
if( pPendingBackground )
{
client.mPendingSend.pCommand = pPendingBackground;
client.mPendingSend.bAutoFree = false;
}
}
//=================================================================================================
// OUTCOM: FRAME
// Transmit a new dearImgui frame to render
//=================================================================================================
void Communications_Outgoing_Frame(ClientInfo& client)
{
CmdDrawFrame* pPendingDraw = client.mPendingFrameOut.Release();
if( pPendingDraw )
{
pPendingDraw->mFrameIndex = client.mFrameIndex++;
//---------------------------------------------------------------------
// Apply delta compression to DrawCommand, when requested
if( pPendingDraw->mCompressed )
{
// Create a new Compressed DrawFrame Command
if( client.mpCmdDrawLast && !client.mServerCompressionSkip ){
client.mpCmdDrawLast->ToPointers();
CmdDrawFrame* pDrawCompressed = CompressCmdDrawFrame(client.mpCmdDrawLast, pPendingDraw);
netImguiDeleteSafe(client.mpCmdDrawLast);
client.mpCmdDrawLast = pPendingDraw; // Keep original new command for next frame delta compression
pPendingDraw = pDrawCompressed; // Request compressed copy to be sent to server
}
// Save DrawCmd for next frame delta compression
else {
pPendingDraw->mCompressed = false;
client.mpCmdDrawLast = pPendingDraw;
}
}
client.mServerCompressionSkip = false;
//---------------------------------------------------------------------
// Ready to send command to server
pPendingDraw->ToOffsets();
client.mPendingSend.pCommand = pPendingDraw;
client.mPendingSend.bAutoFree = client.mpCmdDrawLast != pPendingDraw;
}
}
//=================================================================================================
// OUTCOM: Clipboard
// Send client 'Copy' clipboard content to Server
//=================================================================================================
void Communications_Outgoing_Clipboard(ClientInfo& client)
{
CmdClipboard* pPendingClipboard = client.mPendingClipboardOut.Release();
if( pPendingClipboard ){
pPendingClipboard->ToOffsets();
client.mPendingSend.pCommand = pPendingClipboard;
client.mPendingSend.bAutoFree = true;
}
}
//=================================================================================================
// INCOMING COMMUNICATIONS
//=================================================================================================
void Communications_Incoming(ClientInfo& client)
{
if( Network::DataReceivePending(client.mpSocketComs) )
{
//-----------------------------------------------------------------------------------------
// 1. Ready to receive new command, starts the process by reading Header
//-----------------------------------------------------------------------------------------
if( client.mPendingRcv.IsReady() )
{
client.mCmdPendingRead = CmdPendingRead();
client.mPendingRcv.pCommand = &client.mCmdPendingRead;
client.mPendingRcv.bAutoFree= false;
}
//-----------------------------------------------------------------------------------------
// 2. Read incoming command from server
//-----------------------------------------------------------------------------------------
if( client.mPendingRcv.IsPending() )
{
Network::DataReceive(client.mpSocketComs, client.mPendingRcv);
// Detected a new command bigger than header, allocate memory for it
if( client.mPendingRcv.pCommand->mSize > sizeof(CmdPendingRead) &&
client.mPendingRcv.pCommand == &client.mCmdPendingRead )
{
CmdPendingRead* pCmdHeader = reinterpret_cast<CmdPendingRead*>(netImguiSizedNew<uint8_t>(client.mPendingRcv.pCommand->mSize));
*pCmdHeader = client.mCmdPendingRead;
client.mPendingRcv.pCommand = pCmdHeader;
client.mPendingRcv.bAutoFree= true;
}
}
//-----------------------------------------------------------------------------------------
// 3. Command fully received from Server, process it
//-----------------------------------------------------------------------------------------
if( client.mPendingRcv.IsDone() )
{
if( !client.mPendingRcv.IsError() )
{
switch( client.mPendingRcv.pCommand->mType )
{
case CmdHeader::eCommands::Input: Communications_Incoming_Input(client); break;
case CmdHeader::eCommands::Clipboard: Communications_Incoming_Clipboard(client); break;
// Commands not received in main loop, by Client
case CmdHeader::eCommands::Version:
case CmdHeader::eCommands::Texture:
case CmdHeader::eCommands::DrawFrame:
case CmdHeader::eCommands::Background:
case CmdHeader::eCommands::Count: break;
}
}
// Cleanup after read completion
if( client.mPendingRcv.IsError() ){
client.mbDisconnectPending = true;
}
if( client.mPendingRcv.bAutoFree ){
netImguiDeleteSafe(client.mPendingRcv.pCommand);
}
client.mPendingRcv = PendingCom();
}
}
// Prevent high CPU usage when waiting for new data
else
{
//std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::microseconds(250));
}
}
//=================================================================================================
// OUTGOING COMMUNICATIONS
//=================================================================================================
void Communications_Outgoing(ClientInfo& client)
{
//---------------------------------------------------------------------------------------------
// Try finishing sending a pending command to Server
//---------------------------------------------------------------------------------------------
if( client.mPendingSend.IsPending() )
{
Network::DataSend(client.mpSocketComs, client.mPendingSend);
// Free allocated memory for command
if( client.mPendingSend.IsDone() )
{
if( client.mPendingSend.IsError() ){
client.mbDisconnectPending = true;
}
if( client.mPendingSend.bAutoFree ){
netImguiDeleteSafe(client.mPendingSend.pCommand);
}
client.mPendingSend = PendingCom();
}
}
//---------------------------------------------------------------------------------------------
// Initiate sending next command to Server, when none are in flight
// Keep track of what command was last send to prevent 1 type to monopolize coms
//---------------------------------------------------------------------------------------------
constexpr CmdHeader::eCommands kCommandsOrder[] = {
CmdHeader::eCommands::Texture, CmdHeader::eCommands::Background,
CmdHeader::eCommands::Clipboard, CmdHeader::eCommands::DrawFrame};
constexpr uint32_t kCommandCounts = static_cast<uint32_t>(sizeof(kCommandsOrder)/sizeof(CmdHeader::eCommands));
uint32_t Index(client.mPendingSendNext);
while( client.mPendingSend.IsReady() && (Index-client.mPendingSendNext)<kCommandCounts )
{
CmdHeader::eCommands NextCmd = kCommandsOrder[Index++ % kCommandCounts];
switch( NextCmd )
{
case CmdHeader::eCommands::Texture: Communications_Outgoing_Textures(client); break;
case CmdHeader::eCommands::Background: Communications_Outgoing_Background(client); break;
case CmdHeader::eCommands::Clipboard: Communications_Outgoing_Clipboard(client); break;
case CmdHeader::eCommands::DrawFrame: Communications_Outgoing_Frame(client); break;
// Commands not sent in main loop, by Client
case CmdHeader::eCommands::Input:
case CmdHeader::eCommands::Version:
case CmdHeader::eCommands::Count: break;
}
if( client.mPendingSend.IsPending() ){
client.mPendingSendNext = Index;
}
}
}
//=================================================================================================
// COMMUNICATIONS INITIALIZE
// Initialize a new connection to a RemoteImgui server
//=================================================================================================
bool Communications_Initialize(ClientInfo& client)
{
CmdVersion cmdVersionSend, cmdVersionRcv;
PendingCom PendingRcv, PendingSend;
client.mbComInitActive = true;
//---------------------------------------------------------------------
// Handshake confirming connection validity
//---------------------------------------------------------------------
PendingRcv.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionRcv);
while( !PendingRcv.IsDone() && cmdVersionRcv.mType == CmdHeader::eCommands::Version )
{
while( !client.mbDisconnectPending && !Network::DataReceivePending(client.mpSocketPending) ){
std::this_thread::yield(); // Idle until we receive the remote data
}
Network::DataReceive(client.mpSocketPending, PendingRcv);
}
bool bForceConnect = client.mServerForceConnectEnabled && (cmdVersionRcv.mFlags & static_cast<uint8_t>(CmdVersion::eFlags::ConnectForce)) != 0;
bool bCanConnect = !PendingRcv.IsError() &&
cmdVersionRcv.mType == cmdVersionSend.mType &&
cmdVersionRcv.mVersion == cmdVersionSend.mVersion &&
cmdVersionRcv.mWCharSize == cmdVersionSend.mWCharSize &&
(!client.IsConnected() || bForceConnect);
StringCopy(cmdVersionSend.mClientName, client.mName);
cmdVersionSend.mFlags = client.IsConnected() && !bCanConnect ? static_cast<uint8_t>(CmdVersion::eFlags::IsConnected): 0;
cmdVersionSend.mFlags |= client.IsConnected() && !client.mServerForceConnectEnabled ? static_cast<uint8_t>(CmdVersion::eFlags::IsUnavailable) : 0;
PendingSend.pCommand = reinterpret_cast<CmdPendingRead*>(&cmdVersionSend);
while( !PendingSend.IsDone() ){
Network::DataSend(client.mpSocketPending, PendingSend);
}
//---------------------------------------------------------------------
// Connection established, init client
//---------------------------------------------------------------------
if( bCanConnect && !PendingSend.IsError() && (!client.IsConnected() || bForceConnect) )
{
Network::SocketInfo* pNewComSocket = client.mpSocketPending.exchange(nullptr);
// If we detect an active connection with Server and 'ForceConnect was requested, close it first
if( client.IsConnected() )
{
client.mbDisconnectPending = true;
while( client.IsConnected() );
}
for(auto& texture : client.mTextures)
{
texture.mbSent = false;
}
client.mpSocketComs = pNewComSocket; // Take ownerhip of socket
client.mbHasTextureUpdate = true; // Force sending the client textures
client.mBGSettingSent.mTextureId = client.mBGSetting.mTextureId-1u; // Force sending the Background settings (by making different than current settings)
client.mFrameIndex = 0;
client.mPendingSendNext = 0;
client.mServerForceConnectEnabled = (cmdVersionRcv.mFlags & static_cast<uint8_t>(CmdVersion::eFlags::ConnectExclusive)) == 0;
client.mPendingRcv = PendingCom();
client.mPendingSend = PendingCom();
}
// Disconnect pending socket if init failed
Network::SocketInfo* SocketPending = client.mpSocketPending.exchange(nullptr);
bool bValidConnection = SocketPending == nullptr;
if( SocketPending ){
NetImgui::Internal::Network::Disconnect(SocketPending);
}
client.mbComInitActive = false;
return bValidConnection;
}
//=================================================================================================
// COMMUNICATIONS MAIN LOOP
//=================================================================================================
void Communications_Loop(void* pClientVoid)
{
IM_ASSERT(pClientVoid != nullptr);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
pClient->mbDisconnectPending = false;
pClient->mbClientThreadActive = true;
while( !pClient->mbDisconnectPending )
{
Communications_Outgoing(*pClient);
Communications_Incoming(*pClient);
}
Network::SocketInfo* pSocket = pClient->mpSocketComs.exchange(nullptr);
if (pSocket){
NetImgui::Internal::Network::Disconnect(pSocket);
}
pClient->mbClientThreadActive = false;
}
//=================================================================================================
// COMMUNICATIONS CONNECT THREAD : Reach out and connect to a NetImGuiServer
//=================================================================================================
void CommunicationsConnect(void* pClientVoid)
{
IM_ASSERT(pClientVoid != nullptr);
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
if( Communications_Initialize(*pClient) )
{
Communications_Loop(pClientVoid);
}
}
//=================================================================================================
// COMMUNICATIONS HOST THREAD : Waiting NetImGuiServer reaching out to us.
// Launch a new com loop when connection is established
//=================================================================================================
void CommunicationsHost(void* pClientVoid)
{
ClientInfo* pClient = reinterpret_cast<ClientInfo*>(pClientVoid);
pClient->mbListenThreadActive = true;
pClient->mbDisconnectListen = false;
pClient->mpSocketListen = pClient->mpSocketPending.exchange(nullptr);
while( pClient->mpSocketListen.load() != nullptr && !pClient->mbDisconnectListen )
{
pClient->mpSocketPending = Network::ListenConnect(pClient->mpSocketListen);
if( pClient->mpSocketPending.load() != nullptr && Communications_Initialize(*pClient) )
{
pClient->mThreadFunction(Client::Communications_Loop, pClient);
}
std::this_thread::sleep_for(std::chrono::milliseconds(16)); // Prevents this thread from taking entire core, waiting on server connection requests
}
Network::SocketInfo* pSocket = pClient->mpSocketListen.exchange(nullptr);
NetImgui::Internal::Network::Disconnect(pSocket);
pClient->mbListenThreadActive = false;
}
//=================================================================================================
// Support of the Dear ImGui hooks
// (automatic call to NetImgui::BeginFrame()/EndFrame() on ImGui::BeginFrame()/Imgui::Render()
//=================================================================================================
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
void HookBeginFrame(ImGuiContext*, ImGuiContextHook* hook)
{
Client::ClientInfo& client = *reinterpret_cast<Client::ClientInfo*>(hook->UserData);
if (!client.mbInsideNewEnd)
{
ScopedBool scopedInside(client.mbInsideHook, true);
NetImgui::NewFrame(false);
}
}
void HookEndFrame(ImGuiContext*, ImGuiContextHook* hook)
{
Client::ClientInfo& client = *reinterpret_cast<Client::ClientInfo*>(hook->UserData);
if (!client.mbInsideNewEnd)
{
ScopedBool scopedInside(client.mbInsideHook, true);
NetImgui::EndFrame();
}
}
#endif // NETIMGUI_IMGUI_CALLBACK_ENABLED
//=================================================================================================
// CLIENT INFO Constructor
//=================================================================================================
ClientInfo::ClientInfo()
: mpSocketPending(nullptr)
, mpSocketComs(nullptr)
, mpSocketListen(nullptr)
, mFontTextureID(TextureCastFromUInt(uint64_t(0u)))
, mTexturesPendingSent(0)
, mTexturesPendingCreated(0)
, mbClientThreadActive(false)
, mbListenThreadActive(false)
, mbComInitActive(false)
{
memset(mTexturesPending, 0, sizeof(mTexturesPending));
}
//=================================================================================================
// CLIENT INFO Constructor
//=================================================================================================
ClientInfo::~ClientInfo()
{
ContextRemoveHooks();
for( auto& texture : mTextures ){
texture.Set(nullptr);
}
for(size_t i(0); i<ArrayCount(mTexturesPending); ++i){
netImguiDeleteSafe(mTexturesPending[i]);
}
netImguiDeleteSafe(mpCmdInputPending);
netImguiDeleteSafe(mpCmdDrawLast);
netImguiDeleteSafe(mpCmdClipboard);
}
//=================================================================================================
// Initialize the associated ImguiContext
//=================================================================================================
void ClientInfo::ContextInitialize()
{
mpContext = ImGui::GetCurrentContext();
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
ImGuiContextHook hookNewframe, hookEndframe;
ContextRemoveHooks();
hookNewframe.HookId = 0;
hookNewframe.Type = ImGuiContextHookType_NewFramePre;
hookNewframe.Callback = HookBeginFrame;
hookNewframe.UserData = this;
mhImguiHookNewframe = ImGui::AddContextHook(mpContext, &hookNewframe);
hookEndframe.HookId = 0;
hookEndframe.Type = ImGuiContextHookType_RenderPost;
hookEndframe.Callback = HookEndFrame;
hookEndframe.UserData = this;
mhImguiHookEndframe = ImGui::AddContextHook(mpContext, &hookEndframe);
#endif
}
//=================================================================================================
// Take over a Dear ImGui context for use with NetImgui
//=================================================================================================
void ClientInfo::ContextOverride()
{
ScopedImguiContext scopedSourceCtx(mpContext);
// Keep a copy of original settings of this context
mSavedContextValues.Save(mpContext);
mLastOutgoingDrawCheckTime = std::chrono::steady_clock::now();
// Override some settings
// Note: Make sure every setting overwritten here, are handled in 'SavedImguiContext::Save(...)'
{
ImGuiIO& newIO = ImGui::GetIO();
newIO.MouseDrawCursor = false;
newIO.BackendPlatformName = "NetImgui";
newIO.BackendRendererName = "DirectX11";
if( mFontCreationFunction != nullptr )
{
newIO.FontGlobalScale = 1;
mFontCreationScaling = -1;
}
#if IMGUI_VERSION_NUM < 18700
for (uint32_t i(0); i < ImGuiKey_COUNT; ++i) {
newIO.KeyMap[i] = i;
}
#endif
#if IMGUI_VERSION_NUM < 19110
newIO.GetClipboardTextFn = GetClipboardTextFn_NetImguiImpl;
newIO.SetClipboardTextFn = SetClipboardTextFn_NetImguiImpl;
newIO.ClipboardUserData = this;
#else
ImGuiPlatformIO& platformIO = ImGui::GetPlatformIO();
platformIO.Platform_GetClipboardTextFn = GetClipboardTextFn_NetImguiImpl;
platformIO.Platform_SetClipboardTextFn = SetClipboardTextFn_NetImguiImpl;
platformIO.Platform_ClipboardUserData = this;
#endif
#if defined(IMGUI_HAS_VIEWPORT)
newIO.ConfigFlags &= ~(ImGuiConfigFlags_ViewportsEnable); // Viewport unsupported at the moment
#endif
}
}
//=================================================================================================
// Restore a Dear ImGui context to initial state before we modified it
//=================================================================================================
void ClientInfo::ContextRestore()
{
// Note: only happens if context overriden is same as current one, to prevent trying to restore to a deleted context
if (IsContextOverriden() && ImGui::GetCurrentContext() == mpContext)
{
#ifdef IMGUI_HAS_VIEWPORT
ImGui::UpdatePlatformWindows(); // Prevents issue with mismatched frame tracking, when restoring enabled viewport feature
#endif
if( mFontCreationFunction && ImGui::GetIO().Fonts && ImGui::GetIO().Fonts->Fonts.size() > 0)
{
float noScaleSize = ImGui::GetIO().Fonts->Fonts[0]->FontSize / mFontCreationScaling;
float originalScale = mSavedContextValues.mFontGeneratedSize / noScaleSize;
mFontCreationFunction(mFontCreationScaling, originalScale);
}
mSavedContextValues.Restore(mpContext);
}
}
//=================================================================================================
// Remove callback hooks, once we detect a disconnection
//=================================================================================================
void ClientInfo::ContextRemoveHooks()
{
#if NETIMGUI_IMGUI_CALLBACK_ENABLED
if (mpContext && mhImguiHookNewframe != 0)
{
ImGui::RemoveContextHook(mpContext, mhImguiHookNewframe);
ImGui::RemoveContextHook(mpContext, mhImguiHookEndframe);
mhImguiHookNewframe = mhImguiHookNewframe = 0;
}
#endif
}
//=================================================================================================
// Process textures waiting to be sent to server
// 1. New textures are added tp pending queue (Main Thread)
// 2. Pending textures are sent to Server and added to our active texture list (Com Thread)
//=================================================================================================
void ClientInfo::ProcessTexturePending()
{
while( mTexturesPendingCreated != mTexturesPendingSent )
{
mbHasTextureUpdate |= true;
uint32_t idx = mTexturesPendingSent.fetch_add(1) % static_cast<uint32_t>(ArrayCount(mTexturesPending));
CmdTexture* pCmdTexture = mTexturesPending[idx];
mTexturesPending[idx] = nullptr;
if( pCmdTexture )
{
// Find the TextureId from our list (or free slot)
int texIdx = 0;
int texFreeSlot = static_cast<int>(mTextures.size());
while( texIdx < mTextures.size() && ( !mTextures[texIdx].IsValid() || mTextures[texIdx].mpCmdTexture->mTextureId != pCmdTexture->mTextureId) )
{
texFreeSlot = !mTextures[texIdx].IsValid() ? texIdx : texFreeSlot;
++texIdx;
}
if( texIdx == mTextures.size() )
texIdx = texFreeSlot;
if( texIdx == mTextures.size() )
mTextures.push_back(ClientTexture());
mTextures[texIdx].Set( pCmdTexture );
mTextures[texIdx].mbSent = false;
}
}
}
//=================================================================================================
// Create a new Draw Command from Dear Imgui draw data.
// 1. New ImGui frame has been completed, create a new draw command from draw data (Main Thread)
// 2. We see a pending Draw Command, take ownership of it and send it to Server (Com thread)
//=================================================================================================
void ClientInfo::ProcessDrawData(const ImDrawData* pDearImguiData, ImGuiMouseCursor mouseCursor)
{
if( !mbValidDrawFrame )
return;
CmdDrawFrame* pDrawFrameNew = ConvertToCmdDrawFrame(pDearImguiData, mouseCursor);
pDrawFrameNew->mCompressed = mClientCompressionMode == eCompressionMode::kForceEnable || (mClientCompressionMode == eCompressionMode::kUseServerSetting && mServerCompressionEnabled);
mPendingFrameOut.Assign(pDrawFrameNew);
}
}}} // namespace NetImgui::Internal::Client
#include "NetImgui_WarningReenable.h"
#endif //#if NETIMGUI_ENABLED
@@ -0,0 +1,161 @@
#pragma once
#include "NetImgui_Shared.h"
#include "NetImgui_CmdPackets.h"
//=============================================================================
// Forward Declares
//=============================================================================
namespace NetImgui { namespace Internal { namespace Network { struct SocketInfo; } } }
namespace NetImgui { namespace Internal { namespace Client
{
//=============================================================================
// Keep a list of textures used by Imgui, needed by server
//=============================================================================
struct ClientTexture
{
inline void Set( CmdTexture* pCmdTexture );
inline bool IsValid()const;
CmdTexture* mpCmdTexture= nullptr;
bool mbSent = false;
uint8_t mPadding[7] = {};
};
//=============================================================================
// 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;
float mFontGlobalScale = 1.f;
float mFontGeneratedSize = 0.f;
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
};
//=============================================================================
// Keep all Client infos needed for communication with server
//=============================================================================
struct ClientInfo
{
using VecTexture = ImVector<ClientTexture>;
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;
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
VecTexture mTextures; // List if textures created by this client (used un main thread)
char mName[64] = {};
uint64_t mFrameIndex = 0; // Incremented everytime we send a DrawFrame Command
CmdTexture* mTexturesPending[16] = {};
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 (either the one active when connection request happened, or a clone)
PendingCom mPendingRcv; // Data being currently received from Server
PendingCom mPendingSend; // Data being currently sent to Server
uint32_t mPendingSendNext = 0; // Type of Cmd to next attempt sending, when channel is available
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)
const void* mpFontTextureData = nullptr; // Last font texture data send to server (used to detect if font was changed)
ImTextureID mFontTextureID;
SavedImguiContext mSavedContextValues;
std::atomic_uint32_t mTexturesPendingSent;
std::atomic_uint32_t mTexturesPendingCreated;
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 mbHasTextureUpdate = false;
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 mbFontUploaded = false; // Auto detect if font was sent to server
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
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
float mFontCreationScaling = 1.f; // Last font scaling used when generating the NetImgui font
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;
void ProcessDrawData(const ImDrawData* pDearImguiData, ImGuiMouseCursor mouseCursor);
void ProcessTexturePending();
inline bool IsConnected()const;
inline bool IsConnectPending()const;
inline bool IsActive()const;
// 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 NetImgui::Internal::Client
#include "NetImgui_Client.inl"
@@ -0,0 +1,38 @@
#include "NetImgui_Network.h"
namespace NetImgui { namespace Internal { namespace Client {
void ClientTexture::Set( CmdTexture* pCmdTexture )
{
netImguiDeleteSafe(mpCmdTexture);
mpCmdTexture = pCmdTexture;
mbSent = pCmdTexture == nullptr;
}
bool ClientTexture::IsValid()const
{
return mpCmdTexture != nullptr;
}
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,276 @@
#pragma once
#include "NetImgui_Shared.h"
#include "NetImgui_CmdPackets_DrawFrame.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 mPadding[3] = {};
};
// 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
// 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;
char 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,
// Gamepad (some of those are analog values, 0.0f to 1.0f) // GAME NAVIGATION ACTION
// (download controller mapping PNG/PSD at http://dearimgui.org/controls_sheets)
ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options (PS)
ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS)
ImGuiKey_GamepadFaceLeft, // X (Xbox) Y (Switch) Square (PS) // Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows)
ImGuiKey_GamepadFaceRight, // B (Xbox) A (Switch) Circle (PS) // Cancel / Close / Exit
ImGuiKey_GamepadFaceUp, // Y (Xbox) X (Switch) Triangle (PS) // Text Input / On-screen Keyboard
ImGuiKey_GamepadFaceDown, // A (Xbox) B (Switch) Cross (PS) // 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 (Xbox) L (Switch) L1 (PS) // Tweak Slower / Focus Previous (in Windowing mode)
ImGuiKey_GamepadR1, // R Bumper (Xbox) R (Switch) R1 (PS) // Tweak Faster / Focus Next (in Windowing mode)
ImGuiKey_GamepadL2, // L Trig. (Xbox) ZL (Switch) L2 (PS) [Analog]
ImGuiKey_GamepadR2, // R Trig. (Xbox) ZR (Switch) R2 (PS) [Analog]
ImGuiKey_GamepadL3, // L Stick (Xbox) L3 (Switch) L3 (PS)
ImGuiKey_GamepadR3, // R Stick (Xbox) R3 (Switch) R3 (PS)
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
{
CmdTexture() : CmdHeader(CmdHeader::eCommands::Texture, sizeof(CmdTexture)){}
OffsetPointer<uint8_t> mpTextureData;
uint64_t mTextureId = 0;
uint16_t mWidth = 0;
uint16_t mHeight = 0;
uint8_t mFormat = eTexFormat::kTexFmt_Invalid; // eTexFormat
uint8_t PADDING[3] = {};
};
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 NetImgui::Internal
#include "NetImgui_CmdPackets.inl"
@@ -0,0 +1,101 @@
#include "NetImgui_CmdPackets.h"
namespace NetImgui { namespace Internal
{
void CmdDrawFrame::ToPointers()
{
if( !mpDrawGroups.IsPointer() )
{
mpDrawGroups.ToPointer();
for (uint32_t i(0); i < mDrawGroupCount; ++i) {
mpDrawGroups[i].ToPointers();
}
}
}
void CmdDrawFrame::ToOffsets()
{
if( !mpDrawGroups.IsOffset() )
{
for (uint32_t i(0); i < mDrawGroupCount; ++i) {
mpDrawGroups[i].ToOffsets();
}
mpDrawGroups.ToOffset();
}
}
void ImguiDrawGroup::ToPointers()
{
if( !mpIndices.IsPointer() ) //Safer to test the first element after CmdHeader
{
mpIndices.ToPointer();
mpVertices.ToPointer();
mpDraws.ToPointer();
}
}
void ImguiDrawGroup::ToOffsets()
{
if( !mpIndices.IsOffset() ) //Safer to test the first element after CmdHeader
{
mpIndices.ToOffset();
mpVertices.ToOffset();
mpDraws.ToOffset();
}
}
bool CmdInput::IsKeyDown( CmdInput::NetImguiKeys netimguiKey) const
{
uint32_t valIndex = netimguiKey/64;
uint64_t valMask = 0x0000000000000001ull << (netimguiKey%64);
return mInputDownMask[valIndex] & valMask;
}
bool CmdBackground::operator==(const CmdBackground& cmp)const
{
bool sameValue(true);
for(size_t i(0); i<sizeof(CmdBackground)/8; i++){
sameValue &= reinterpret_cast<const uint64_t*>(this)[i] == reinterpret_cast<const uint64_t*>(&cmp)[i];
}
return sameValue;
}
bool CmdBackground::operator!=(const CmdBackground& cmp)const
{
return (*this == cmp) == false;
}
void CmdClipboard::ToPointers()
{
if( !mContentUTF8.IsPointer() ){
mContentUTF8.ToPointer();
}
}
void CmdClipboard::ToOffsets()
{
if( !mContentUTF8.IsOffset() ){
mContentUTF8.ToOffset();
}
}
CmdClipboard* CmdClipboard::Create(const char* clipboard)
{
if( clipboard )
{
size_t clipboardByteSize(0);
while(clipboard[clipboardByteSize++] != 0);
size_t totalDataCount = sizeof(CmdClipboard) + DivUp<size_t>(clipboardByteSize, ComDataSize);
auto pNewClipboard = NetImgui::Internal::netImguiSizedNew<CmdClipboard>(totalDataCount*ComDataSize);
pNewClipboard->mSize = static_cast<uint32_t>(totalDataCount*ComDataSize);
pNewClipboard->mByteSize = clipboardByteSize;
pNewClipboard->mContentUTF8.SetPtr(reinterpret_cast<char*>(&pNewClipboard[1]));
memcpy(pNewClipboard->mContentUTF8.Get(), clipboard, clipboardByteSize);
return pNewClipboard;
}
return nullptr;
}
}} // namespace NetImgui::Internal
@@ -0,0 +1,381 @@
#include "NetImgui_Shared.h"
#if NETIMGUI_ENABLED
#include "NetImgui_WarningDisable.h"
#include "NetImgui_CmdPackets.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
pOutDraws[drawCount].mTextureId = TextureCastFromID(pCmd->TextureId);
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 a data stream and output a version with only the difference from other stream is written
//=================================================================================================
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 NetImgui::Internal
#include "NetImgui_WarningReenable.h"
#endif //#if NETIMGUI_ENABLED
@@ -0,0 +1,50 @@
#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
{
uint64_t mTextureId;
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 NetImgui::Internal
@@ -0,0 +1,22 @@
#pragma once
namespace NetImgui { namespace Internal { struct PendingCom; }}
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 NetImgui::Internal::Network
@@ -0,0 +1,154 @@
#include "NetImgui_Shared.h"
#if defined(_MSC_VER)
#pragma warning (disable: 4221)
#endif
#if NETIMGUI_ENABLED && NETIMGUI_POSIX_SOCKETS_ENABLED
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
//NOTE: The socket handling has been modified to improve speed, but the Posix version
// has not been updated. Please review the changes made to 'NetImgui_NetworkWin32.cpp'
// between version 1.11 and 1.12 and bring them over to this file. In particular :
// -Sockets set to non blocking and immediate sending
// -Added 'DataReceivePending' function
// -Reworked 'DataReceive' and 'DataSend' to be non blocking socket operation
static_assert(0)
namespace NetImgui { namespace Internal { namespace Network
{
struct SocketInfo
{
SocketInfo(int socket) : mSocket(socket){}
int mSocket;
};
bool Startup()
{
return true;
}
void Shutdown()
{
}
inline void SetNonBlocking(int Socket, bool bIsNonBlocking)
{
int Flags = fcntl(Socket, F_GETFL, 0);
Flags = bIsNonBlocking ? Flags | O_NONBLOCK : Flags ^ (Flags & O_NONBLOCK);
fcntl(Socket, F_SETFL, Flags);
}
SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort)
{
int ConnectSocket = socket(AF_INET , SOCK_STREAM , 0 );
if(ConnectSocket == -1)
return nullptr;
char zPortName[32];
addrinfo* pResults = nullptr;
SocketInfo* pSocketInfo = nullptr;
sprintf(zPortName, "%i", ServerPort);
getaddrinfo(ServerHost, zPortName, nullptr, &pResults);
addrinfo* pResultCur = pResults;
while( pResultCur && !pSocketInfo)
{
if( connect(ConnectSocket, pResultCur->ai_addr, static_cast<int>(pResultCur->ai_addrlen)) == 0 )
{
SetNonBlocking(ConnectSocket, false);
pSocketInfo = netImguiNew<SocketInfo>(ConnectSocket);
}
pResultCur = pResultCur->ai_next;
}
freeaddrinfo(pResults);
if( !pSocketInfo )
{
close(ConnectSocket);
}
return pSocketInfo;
}
SocketInfo* ListenStart(uint32_t ListenPort)
{
addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
addrinfo* addrInfo;
getaddrinfo(nullptr, std::to_string(ListenPort).c_str(), &hints, &addrInfo);
int 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));
setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag));
#endif
if( bind(ListenSocket, addrInfo->ai_addr, addrInfo->ai_addrlen) != -1 &&
listen(ListenSocket, 0) != -1)
{
SetNonBlocking(ListenSocket, false);
return netImguiNew<SocketInfo>(ListenSocket);
}
close(ListenSocket);
}
return nullptr;
}
SocketInfo* ListenConnect(SocketInfo* ListenSocket)
{
if( ListenSocket )
{
sockaddr_storage ClientAddress;
socklen_t Size(sizeof(ClientAddress));
int ServerSocket = accept(ListenSocket->mSocket, (sockaddr*)&ClientAddress, &Size) ;
if (ServerSocket != -1)
{
SetNonBlocking(ServerSocket, false);
return netImguiNew<SocketInfo>(ServerSocket);
}
}
return nullptr;
}
void Disconnect(SocketInfo* pClientSocket)
{
if( pClientSocket )
{
shutdown(pClientSocket->mSocket, SHUT_RDWR);
close(pClientSocket->mSocket);
netImguiDelete(pClientSocket);
}
}
bool DataReceive(SocketInfo* pClientSocket, void* pDataIn, size_t Size)
{
int resultRcv = recv(pClientSocket->mSocket, static_cast<char*>(pDataIn), static_cast<int>(Size), MSG_WAITALL);
return static_cast<int>(Size) == resultRcv;
}
bool DataSend(SocketInfo* pClientSocket, void* pDataOut, size_t Size)
{
int resultSend = send(pClientSocket->mSocket, static_cast<char*>(pDataOut), static_cast<int>(Size), 0);
return static_cast<int>(Size) == resultSend;
}
}}} // namespace NetImgui::Internal::Network
#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,233 @@
#include "NetImgui_Shared.h"
// Tested with Unreal Engine 4.27, 5.3, 5.4, 5.5
#if NETIMGUI_ENABLED && defined(__UNREAL__)
#include "CoreMinimal.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Misc/OutputDeviceRedirector.h"
#include "SocketSubsystem.h"
#include "Sockets.h"
#include "HAL/PlatformProcess.h"
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 2
#include "IPAddressAsyncResolve.h"
#endif
namespace NetImgui { namespace Internal { namespace Network
{
//=================================================================================================
// Wrapper around native socket object and init some socket options
//=================================================================================================
struct SocketInfo
{
SocketInfo(FSocket* pSocket)
: mpSocket(pSocket)
{
if( mpSocket )
{
mpSocket->SetNonBlocking(true);
mpSocket->SetNoDelay(true);
int32 NewSize(0);
while( !mpSocket->SetSendBufferSize(2*mSendSize, NewSize) ){
mSendSize /= 2;
}
mSendSize = NewSize/2;
}
}
~SocketInfo()
{
Close();
}
void Close()
{
if(mpSocket )
{
mpSocket->Close();
ISocketSubsystem::Get()->DestroySocket(mpSocket);
mpSocket = nullptr;
}
}
FSocket* mpSocket = nullptr;
int32 mSendSize = 1024*1024; // Limit tx data to avoid socket error on large amount (texture)
};
bool Startup()
{
return true;
}
void Shutdown()
{
}
//=================================================================================================
// Try establishing a connection to a remote client at given address
//=================================================================================================
SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort)
{
SocketInfo* pSocketInfo = nullptr;
ISocketSubsystem* SocketSubSystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
TSharedPtr<FInternetAddr> IpAddressFind = SocketSubSystem->GetAddressFromString((TCHAR*)StringCast<TCHAR>(static_cast<const ANSICHAR*>(ServerHost)).Get());
if(IpAddressFind)
{
TSharedRef<FInternetAddr> IpAddress = IpAddressFind->Clone();
IpAddress->SetPort(ServerPort);
if (IpAddress->IsValid())
{
FSocket* pNewSocket = SocketSubSystem->CreateSocket(NAME_Stream, "NetImgui", IpAddress->GetProtocolType());
if (pNewSocket)
{
pNewSocket->SetNonBlocking(true);
if (pNewSocket->Connect(*IpAddress))
{
bool bConnectionReady = false;
pNewSocket->WaitForPendingConnection(bConnectionReady, FTimespan::FromSeconds(1.0f));
if( bConnectionReady )
{
pSocketInfo = netImguiNew<SocketInfo>(pNewSocket);
return pSocketInfo;
}
}
}
}
}
netImguiDelete(pSocketInfo);
return nullptr;
}
//=================================================================================================
// Start waiting for connection request on this socket
//=================================================================================================
SocketInfo* ListenStart(uint32_t ListenPort)
{
ISocketSubsystem* PlatformSocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
TSharedPtr<FInternetAddr> IpAddress = PlatformSocketSub->GetLocalBindAddr(*GLog);
IpAddress->SetPort(ListenPort);
FSocket* pNewListenSocket = PlatformSocketSub->CreateSocket(NAME_Stream, "NetImguiListen", IpAddress->GetProtocolType());
if( pNewListenSocket )
{
SocketInfo* pListenSocketInfo = netImguiNew<SocketInfo>(pNewListenSocket);
#if NETIMGUI_FORCE_TCP_LISTEN_BINDING
pNewListenSocket->SetReuseAddr();
#endif
pNewListenSocket->SetNonBlocking(false);
pNewListenSocket->SetRecvErr();
if (pNewListenSocket->Bind(*IpAddress))
{
if (pNewListenSocket->Listen(1))
{
return pListenSocketInfo;
}
}
netImguiDelete(pListenSocketInfo);
}
return nullptr;
}
//=================================================================================================
// Establish a new connection to a remote request
//=================================================================================================
SocketInfo* ListenConnect(SocketInfo* pListenSocket)
{
if (pListenSocket)
{
FSocket* pNewSocket = pListenSocket->mpSocket->Accept(FString("NetImgui"));
if( pNewSocket )
{
SocketInfo* pSocketInfo = netImguiNew<SocketInfo>(pNewSocket);
return pSocketInfo;
}
}
return nullptr;
}
//=================================================================================================
// Close a connection and free allocated object
//=================================================================================================
void Disconnect(SocketInfo* pClientSocket)
{
netImguiDelete(pClientSocket);
}
//=================================================================================================
// Return true if data has been received, or there's a connection error
//=================================================================================================
bool DataReceivePending(SocketInfo* pClientSocket)
{
// Note: return true on a connection error, to exit code looping on the data wait. Will handle error after DataReceive()
uint32 PendingDataSize;
return !pClientSocket || pClientSocket->mpSocket->HasPendingData(PendingDataSize) || (pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected);
}
//=================================================================================================
// 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->mpSocket || (pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected)){
PendingComRcv.bError = true;
return;
}
int32 sizeRcv(0);
if( pClientSocket->mpSocket->Recv( &reinterpret_cast<uint8*>(PendingComRcv.pCommand)[PendingComRcv.SizeCurrent],
static_cast<int>(PendingComRcv.pCommand->mSize-PendingComRcv.SizeCurrent),
sizeRcv,
ESocketReceiveFlags::None) )
{
PendingComRcv.SizeCurrent += static_cast<size_t>(sizeRcv);
PendingComRcv.bError |= sizeRcv <= 0; // Error if no data read since DataReceivePending() said there was some available
}
else
{
// Connection error, abort transmission
const ESocketErrors SocketError = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLastErrorCode();
PendingComRcv.bError = SocketError != SE_NO_ERROR && SocketError != ESocketErrors::SE_EWOULDBLOCK;
}
}
//=================================================================================================
// 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->mpSocket || (pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected) ){
PendingComSend.bError = true;
return;
}
int32 sizeSent = 0;
int32 sizeToSend = PendingComSend.pCommand->mSize-PendingComSend.SizeCurrent;
sizeToSend = sizeToSend > pClientSocket->mSendSize ? pClientSocket->mSendSize : sizeToSend;
if( pClientSocket->mpSocket->Send( &reinterpret_cast<uint8*>(PendingComSend.pCommand)[PendingComSend.SizeCurrent],
static_cast<int>(sizeToSend),
sizeSent) )
{
PendingComSend.SizeCurrent += static_cast<size_t>(sizeSent);
}
else
{
// Connection error, abort transmission
const ESocketErrors SocketError = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLastErrorCode();
PendingComSend.bError = SocketError != SE_NO_ERROR && SocketError != ESocketErrors::SE_EWOULDBLOCK;
}
}
}}} // namespace NetImgui::Internal::Network
#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_NetworkUE4;
int sSuppresstLNK4221_NetImgui_NetworkUE4(0);
#endif // #if NETIMGUI_ENABLED && defined(__UNREAL__)
@@ -0,0 +1,253 @@
#include "NetImgui_Shared.h"
#if NETIMGUI_ENABLED && NETIMGUI_WINSOCKET_ENABLED
#include "NetImgui_WarningDisableStd.h"
#include <WinSock2.h>
#include <WS2tcpip.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 NetImgui::Internal::Network
#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,200 @@
#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 "NetImgui_WarningDisableStd.h"
#include <atomic>
#include <thread>
#include <chrono>
#include <vector>
#include "NetImgui_WarningReenable.h"
//=================================================================================================
//=================================================================================================
#include "NetImgui_WarningDisable.h"
namespace NetImgui { namespace Internal
{
using ComDataType = uint64_t;
constexpr size_t ComDataSize = sizeof(ComDataType);
//=============================================================================
// 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);
inline uint64_t TextureCastFromID(ImTextureID textureID);
inline ImTextureID TextureCastFromPtr(void* pTexture);
inline ImTextureID TextureCastFromUInt(uint64_t textureID);
}} //namespace NetImgui::Internal
#include "NetImgui_Shared.inl"
#include "NetImgui_WarningReenable.h"
#endif //NETIMGUI_ENABLED
@@ -0,0 +1,319 @@
#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
{
ImTextureID TextureID;
uint64_t TextureUint;
const void* TexturePtr;
};
uint64_t TextureCastFromID(ImTextureID textureID)
{
TextureCastHelperUnion textureUnion;
textureUnion.TextureUint = 0;
textureUnion.TextureID = textureID;
return textureUnion.TextureUint;
}
ImTextureID TextureCastFromPtr(void* pTexture)
{
TextureCastHelperUnion textureUnion;
textureUnion.TextureUint = 0;
textureUnion.TexturePtr = pTexture;
return textureUnion.TextureID;
}
#ifndef IS_NETIMGUISERVER
ImTextureID TextureCastFromUInt(uint64_t textureID)
{
TextureCastHelperUnion textureUnion;
textureUnion.TextureUint = textureID;
return textureUnion.TextureID;
}
#endif
}} //namespace NetImgui::Internal
@@ -0,0 +1,33 @@
#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 "-Wc++98-compat-pedantic"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning (disable: 5032) // detected #pragma warning(push) with no corresponding #pragma warning(pop)
#pragma warning (push)
#pragma warning (disable: 4365) // conversion from 'long' to 'unsigned int', signed/unsigned mismatch for <atomic>
#pragma warning (disable: 4464) // relative include path contains '..'
#pragma warning (disable: 4514) // unreferenced inline function has been removed
#pragma warning (disable: 4577) // 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify
#pragma warning (disable: 4710) // 'xxx': function not inlined
#pragma warning (disable: 4711) // function 'xxx' selected for automatic inline expansion
#pragma warning (disable: 4826) // Conversion from 'TType *' to 'uint64_t' is sign-extended. This may cause unexpected runtime behavior.
#pragma warning (disable: 5031) // #pragma warning(pop): likely mismatch, popping warning state pushed in different file
#pragma warning (disable: 5045) // Compiler will insert Spectre mitigation for memory load if / Qspectre switch specified
#pragma warning (disable: 5264) // 'xxx': 'const' variable is not used
#endif
@@ -0,0 +1,33 @@
#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" // Enuma 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,25 @@
#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,18 @@
#pragma once
//=================================================================================================
// Clang
//=================================================================================================
#if defined(__clang__)
#pragma clang diagnostic pop
//=================================================================================================
// Visual Studio warnings
//=================================================================================================
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif
@@ -631,7 +631,7 @@ void FCogAbilityWindow_Abilities::RenderAbilityInfo(const UAbilitySystemComponen
ImGui::TableNextColumn();
ImGui::TextColored(TextColor, "Ability Tags");
ImGui::TableNextColumn();
FCogAbilityHelper::RenderTagContainer(Ability->AbilityTags);
FCogAbilityHelper::RenderTagContainer(Ability->GetAssetTags());
//------------------------
// RequiredTags
@@ -19,6 +19,7 @@
#include "CogEngineWindow_LogCategories.h"
#include "CogEngineWindow_Metrics.h"
#include "CogEngineWindow_NetEmulation.h"
#include "CogEngineWindow_NetImGui.h"
#include "CogEngineWindow_OutputLog.h"
#include "CogEngineWindow_Plots.h"
#include "CogEngineWindow_Scalability.h"
@@ -65,11 +66,13 @@ void Cog::AddAllWindows(UCogWindowManager& CogWindowManager)
CogWindowManager.AddWindow<FCogEngineWindow_LogCategories>("Engine.Log Categories");
CogWindowManager.AddWindow<FCogEngineWindow_Metrics>("Engine.Metrics");
CogWindowManager.AddWindow<FCogEngineWindow_NetEmulation>("Engine.Net Emulation");
CogWindowManager.AddWindow<FCogEngineWindow_OutputLog>("Engine.Output Log");
CogWindowManager.AddWindow<FCogEngineWindow_NetImGui>("Engine.NetImGui");
CogWindowManager.AddWindow<FCogEngineWindow_Metrics>("Engine.Metrics");
CogWindowManager.AddWindow<FCogEngineWindow_OutputLog>("Engine.Output Log");
CogWindowManager.AddWindow<FCogEngineWindow_Plots>("Engine.Plots");