From 7a44e1066013d8e7b18fa31993e673f5bc74cad8 Mon Sep 17 00:00:00 2001 From: Sammy Fatnassi Date: Sun, 26 May 2024 17:06:07 -0400 Subject: [PATCH] Added 'TakeOver' Option NetImguiServerApp can now forcefully take over a connection to a client already connected to another server. A button with 'TakeOver' appear in the Client List letting the user connect despite an active connection. --- Code/Client/NetImgui_Api.h | 8 +- Code/Client/Private/NetImgui_Api.cpp | 8 +- Code/Client/Private/NetImgui_Client.cpp | 128 +++++++++++------- Code/Client/Private/NetImgui_Client.h | 7 +- Code/Client/Private/NetImgui_CmdPackets.h | 12 +- Code/Client/Private/NetImgui_Shared.h | 1 + .../SampleCompression/SampleCompression.cpp | 2 +- .../Source/NetImguiServer_Config.cpp | 47 +++++-- Code/ServerApp/Source/NetImguiServer_Config.h | 46 ++++--- .../Source/NetImguiServer_Network.cpp | 50 ++++--- Code/ServerApp/Source/NetImguiServer_UI.cpp | 44 +++++- 11 files changed, 240 insertions(+), 113 deletions(-) diff --git a/Code/Client/NetImgui_Api.h b/Code/Client/NetImgui_Api.h index 770021c..41ed226 100644 --- a/Code/Client/NetImgui_Api.h +++ b/Code/Client/NetImgui_Api.h @@ -4,12 +4,12 @@ //! @Name : NetImgui //================================================================================================= //! @author : Sammy Fatnassi -//! @date : 2023/12/2 +//! @date : 2024/05/26 //! @version : v1.9.7 //! @Details : For integration info : https://github.com/sammyfreg/netImgui/wiki //================================================================================================= -#define NETIMGUI_VERSION "1.9.7" // Update to samples, texture creation fix -#define NETIMGUI_VERSION_NUM 10907 +#define NETIMGUI_VERSION "1.9.8" // Added 'TakeOver' connection option +#define NETIMGUI_VERSION_NUM 10908 @@ -62,7 +62,7 @@ // (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 + #define NETIMGUI_ENABLED 0 #endif #if NETIMGUI_ENABLED diff --git a/Code/Client/Private/NetImgui_Api.cpp b/Code/Client/Private/NetImgui_Api.cpp index 7b1c9b6..a44f9fa 100644 --- a/Code/Client/Private/NetImgui_Api.cpp +++ b/Code/Client/Private/NetImgui_Api.cpp @@ -56,8 +56,8 @@ bool ConnectToApp(const char* clientName, const char* ServerHost, uint32_t serve if (client.mpSocketPending.load() != nullptr) { client.ContextInitialize(); - threadFunction = threadFunction == nullptr ? DefaultStartCommunicationThread : threadFunction; - threadFunction(Client::CommunicationsClient, &client); + threadFunction = threadFunction == nullptr ? DefaultStartCommunicationThread : threadFunction; + threadFunction(Client::CommunicationsConnect, &client); } return client.IsActive(); @@ -81,11 +81,11 @@ bool ConnectFromApp(const char* clientName, uint32_t serverPort, ThreadFunctPtr 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(); - threadFunction = threadFunction == nullptr ? DefaultStartCommunicationThread : threadFunction; - threadFunction(Client::CommunicationsHost, &client); + client.mThreadFunction(Client::CommunicationsHost, &client); } return client.IsActive(); diff --git a/Code/Client/Private/NetImgui_Client.cpp b/Code/Client/Private/NetImgui_Client.cpp index a659713..b60ab90 100644 --- a/Code/Client/Private/NetImgui_Client.cpp +++ b/Code/Client/Private/NetImgui_Client.cpp @@ -75,35 +75,6 @@ static void SetClipboardTextFn_NetImguiImpl(void* user_data_ctx, const char* tex } } -//================================================================================================= -// COMMUNICATIONS INITIALIZE -// Initialize a new connection to a RemoteImgui server -//================================================================================================= -bool Communications_Initialize(ClientInfo& client) -{ - CmdVersion cmdVersionSend, cmdVersionRcv; - StringCopy(cmdVersionSend.mClientName, client.mName); - bool bResultSend = Network::DataSend(client.mpSocketPending, &cmdVersionSend, cmdVersionSend.mHeader.mSize); - bool bResultRcv = Network::DataReceive(client.mpSocketPending, &cmdVersionRcv, sizeof(cmdVersionRcv)); - bool mbConnected = bResultRcv && bResultSend && - cmdVersionRcv.mHeader.mType == cmdVersionSend.mHeader.mType && - cmdVersionRcv.mVersion == cmdVersionSend.mVersion && - cmdVersionRcv.mWCharSize == cmdVersionSend.mWCharSize; - if(mbConnected) - { - for(auto& texture : client.mTextures) - { - texture.mbSent = false; - } - - 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.mpSocketComs = client.mpSocketPending.exchange(nullptr); - client.mFrameIndex = 0; - } - return client.mpSocketComs.load() != nullptr; -} - //================================================================================================= // INCOM: INPUT // Receive new keyboard/mouse/screen resolution input to pass on to dearImgui @@ -332,16 +303,66 @@ bool Communications_Outgoing(ClientInfo& client) } //================================================================================================= -// COMMUNICATIONS THREAD +// COMMUNICATIONS INITIALIZE +// Initialize a new connection to a RemoteImgui server //================================================================================================= -void CommunicationsClient(void* pClientVoid) -{ +bool Communications_Initialize(ClientInfo& client) +{ + CmdVersion cmdVersionSend, cmdVersionRcv; + bool bResultRcv = Network::DataReceive(client.mpSocketPending, &cmdVersionRcv, sizeof(cmdVersionRcv)); + bool bForceConnect = client.mServerForceConnectEnabled && (cmdVersionRcv.mFlags & static_cast(CmdVersion::eFlags::ConnectForce)) != 0; + bool bCanConnect = bResultRcv && + cmdVersionRcv.mHeader.mType == cmdVersionSend.mHeader.mType && + cmdVersionRcv.mVersion == cmdVersionSend.mVersion && + cmdVersionRcv.mWCharSize == cmdVersionSend.mWCharSize && + (!client.IsConnected() || bForceConnect); + + StringCopy(cmdVersionSend.mClientName, client.mName); + cmdVersionSend.mFlags = client.IsConnected() && !bCanConnect ? static_cast(CmdVersion::eFlags::IsConnected): 0; + cmdVersionSend.mFlags |= client.IsConnected() && !client.mServerForceConnectEnabled ? static_cast(CmdVersion::eFlags::IsUnavailable) : 0; + bool bResultSend = Network::DataSend(client.mpSocketPending, &cmdVersionSend, cmdVersionSend.mHeader.mSize); + + if(bCanConnect && bResultSend) + { + Network::SocketInfo* pNewConnect = client.mpSocketPending.exchange(nullptr); + if( client.IsConnected() ) + { + if( bForceConnect ) + { + client.mbDisconnectRequest = true; + while( client.IsConnected() ); + } + else + { + NetImgui::Internal::Network::Disconnect(pNewConnect); + return false; + } + } + + for(auto& texture : client.mTextures) + { + texture.mbSent = false; + } + 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.mpSocketComs = pNewConnect; + client.mFrameIndex = 0; + client.mServerForceConnectEnabled = (cmdVersionRcv.mFlags & static_cast(CmdVersion::eFlags::ConnectExclusive)) == 0; + } + return client.mpSocketPending.load() == nullptr; +} + +//================================================================================================= +// COMMUNICATIONS MAIN LOOP +//================================================================================================= +void Communications_Loop(void* pClientVoid) +{ + IM_ASSERT(pClientVoid != nullptr); ClientInfo* pClient = reinterpret_cast(pClientVoid); - pClient->mbClientThreadActive = true; - pClient->mbDisconnectRequest = false; - Communications_Initialize(*pClient); bool bConnected = pClient->IsConnected(); - + pClient->mbDisconnectRequest = false; + pClient->mbClientThreadActive = true; + while( bConnected && !pClient->mbDisconnectRequest ) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -350,11 +371,27 @@ void CommunicationsClient(void* pClientVoid) } pClient->KillSocketComs(); - pClient->mbClientThreadActive = false; + 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(pClientVoid); + pClient->mbClientThreadActive = true; + if( Communications_Initialize(*pClient) ) + { + Communications_Loop(pClientVoid); + } + pClient->mbClientThreadActive = false; } //================================================================================================= -// COMMUNICATIONS WAIT THREAD +// COMMUNICATIONS HOST THREAD : Waiting NetImGuiServer reaching out to us. +// Launch a new com loop when connection is established //================================================================================================= void CommunicationsHost(void* pClientVoid) { @@ -366,18 +403,13 @@ void CommunicationsHost(void* pClientVoid) while( pClient->mpSocketListen.load() != nullptr && !pClient->mbDisconnectRequest ) { pClient->mpSocketPending = Network::ListenConnect(pClient->mpSocketListen); - if( pClient->mpSocketPending.load() != nullptr ) + if( !pClient->mbDisconnectRequest && + pClient->mpSocketPending.load() != nullptr && + Communications_Initialize(*pClient) ) { - bool bConnected = Communications_Initialize(*pClient); - while (bConnected && !pClient->mbDisconnectRequest) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - //std::this_thread::yield(); - bConnected = Communications_Outgoing(*pClient) && Communications_Incoming(*pClient); - } - pClient->KillSocketComs(); + pClient->mThreadFunction(Client::Communications_Loop, pClient); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Prevents this thread from taking entire core, waiting on server connection + std::this_thread::sleep_for(std::chrono::milliseconds(16)); // Prevents this thread from taking entire core, waiting on server connection } pClient->KillSocketListen(); pClient->mbListenThreadActive = false; diff --git a/Code/Client/Private/NetImgui_Client.h b/Code/Client/Private/NetImgui_Client.h index cee74cc..a5d05d0 100644 --- a/Code/Client/Private/NetImgui_Client.h +++ b/Code/Client/Private/NetImgui_Client.h @@ -113,7 +113,8 @@ struct ClientInfo 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 - char PADDING[1]; + 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 FontCreationFuncPtr 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 InputState mPreviousInputState; // Keeping track of last keyboard/mouse state @@ -136,9 +137,9 @@ struct ClientInfo }; //============================================================================= -// Main communication thread, that should be started in its own thread +// Main communication loop threads that are run in separate threads //============================================================================= -void CommunicationsClient(void* pClientVoid); +void CommunicationsConnect(void* pClientVoid); void CommunicationsHost(void* pClientVoid); }}} //namespace NetImgui::Internal::Client diff --git a/Code/Client/Private/NetImgui_CmdPackets.h b/Code/Client/Private/NetImgui_CmdPackets.h index 907a0d2..ff66d46 100644 --- a/Code/Client/Private/NetImgui_CmdPackets.h +++ b/Code/Client/Private/NetImgui_CmdPackets.h @@ -46,13 +46,20 @@ struct alignas(8) CmdVersion 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 // 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 + }; CmdHeader mHeader = CmdHeader(CmdHeader::eCommands::Version, sizeof(CmdVersion)); eVersion mVersion = eVersion::_current; char mClientName[64] = {}; @@ -61,7 +68,8 @@ struct alignas(8) CmdVersion uint32_t mImguiVerID = IMGUI_VERSION_NUM; uint32_t mNetImguiVerID = NETIMGUI_VERSION_NUM; uint8_t mWCharSize = static_cast(sizeof(ImWchar)); - char PADDING[3]; + uint8_t mFlags = 0; + char PADDING[2]; }; struct alignas(8) CmdInput diff --git a/Code/Client/Private/NetImgui_Shared.h b/Code/Client/Private/NetImgui_Shared.h index adb042c..c368e53 100644 --- a/Code/Client/Private/NetImgui_Shared.h +++ b/Code/Client/Private/NetImgui_Shared.h @@ -94,6 +94,7 @@ class ExchangePtr inline TType* Release(); inline void Assign(TType*& pNewData); inline void Free(); + inline bool IsNull()const { return mpData.load() != nullptr; } private: std::atomic mpData; diff --git a/Code/Sample/SampleCompression/SampleCompression.cpp b/Code/Sample/SampleCompression/SampleCompression.cpp index c720886..f34b16f 100644 --- a/Code/Sample/SampleCompression/SampleCompression.cpp +++ b/Code/Sample/SampleCompression/SampleCompression.cpp @@ -90,7 +90,7 @@ void CustomThreadLauncher(void ComFunctPtr(void*), void* pClient) #endif //========================================================================================= // @SAMPLE_EDIT - if( ComFunctPtr == NetImgui::Internal::Client::CommunicationsClient ){ + if( ComFunctPtr == NetImgui::Internal::Client::CommunicationsConnect ){ std::thread(CustomCommunicationsClient, pClient).detach(); gMetric_ConnectTo = true; return; diff --git a/Code/ServerApp/Source/NetImguiServer_Config.cpp b/Code/ServerApp/Source/NetImguiServer_Config.cpp index f981fc3..4be63b0 100644 --- a/Code/ServerApp/Source/NetImguiServer_Config.cpp +++ b/Code/ServerApp/Source/NetImguiServer_Config.cpp @@ -25,6 +25,7 @@ static constexpr char kConfigField_Name[] = "Name"; static constexpr char kConfigField_Hostname[] = "Hostname"; static constexpr char kConfigField_Hostport[] = "HostPort"; static constexpr char kConfigField_AutoConnect[] = "Auto"; +static constexpr char kConfigField_BlockTakeover[] = "BlockTakeover"; static constexpr char kConfigField_DPIScaleEnabled[] = "DPIScaleEnabled"; uint32_t Server::sPort = NetImgui::kDefaultServerPort; @@ -87,9 +88,11 @@ Client::Client() , mRuntimeID(kInvalidRuntimeID) , mConnectAuto(false) , mConnectRequest(false) -, mConnected(false) +, mConnectForce(false) , mConfigType(NetImguiServer::Config::Client::eConfigType::Pending) , mDPIScaleEnabled(true) +, mBlockTakeover(false) +, mStatus(eStatus::Disconnected) { NetImgui::Internal::StringCopy(mClientName, "New Client"); NetImgui::Internal::StringCopy(mHostName, "localhost"); @@ -159,10 +162,6 @@ bool Client::GetConfigByID(uint32_t configID, Client& config) } //================================================================================================= -// Warning: This is not multithread safe. -// While no crash will occurs, it is possible for a config to change position between 2 calls. -// -// Only use if there's no problem iterating over the same item 2x, or miss it entirely bool Client::GetConfigByIndex(uint32_t index, Client& config) //================================================================================================= { @@ -184,31 +183,49 @@ uint32_t Client::GetConfigCount() } //================================================================================================= -void Client::SetProperty_Connected(uint32_t configID, bool value) +bool Client::GetProperty_BlockTakeover(uint32_t configID) //================================================================================================= { std::lock_guard guard(gConfigLock); int index = FindClientIndex(configID); - if( index != -1 ) - gConfigList[index]->mConnected = value; + if( index != -1 ){ + return gConfigList[index]->mBlockTakeover; + } + return false; +} + +//================================================================================================= +void Client::SetProperty_Status(uint32_t configID, eStatus Status) +//================================================================================================= +{ + std::lock_guard guard(gConfigLock); + int index = FindClientIndex(configID); + if( index != -1 ){ + gConfigList[index]->mStatus = Status; + } } //================================================================================================= void Client::SetProperty_ConnectAuto(uint32_t configID, bool value) //================================================================================================= { + std::lock_guard guard(gConfigLock); int index = FindClientIndex(configID); - if (index != -1) + if (index != -1){ gConfigList[index]->mConnectAuto = value; + } } //================================================================================================= -void Client::SetProperty_ConnectRequest(uint32_t configID, bool value) +void Client::SetProperty_ConnectRequest(uint32_t configID, bool value, bool force) //================================================================================================= { + std::lock_guard guard(gConfigLock); int index = FindClientIndex(configID); - if (index != -1) - gConfigList[index]->mConnectRequest = value; + if (index != -1){ + gConfigList[index]->mConnectRequest = value && !force; + gConfigList[index]->mConnectForce = value && force; + } } //================================================================================================= @@ -266,6 +283,7 @@ void Client::SaveConfigFile(eConfigType configFileType, bool writeServerSettings config[kConfigField_Hostname] = pConfig->mHostName; config[kConfigField_Hostport] = pConfig->mHostPort; config[kConfigField_AutoConnect] = pConfig->mConnectAuto; + config[kConfigField_BlockTakeover] = pConfig->mBlockTakeover; config[kConfigField_DPIScaleEnabled] = pConfig->mDPIScaleEnabled; } } @@ -299,7 +317,7 @@ void Client::LoadAll() //================================================================================================= void Client::LoadConfigFile(eConfigType configFileType) //================================================================================================= -{ +{ nlohmann::json configRoot; const char* filename = GetConfigFilename(configFileType); std::ifstream inputFile(filename); @@ -333,10 +351,11 @@ void Client::LoadConfigFile(eConfigType configFileType) if( config.find(kConfigField_Hostname) != config.end() ) NetImgui::Internal::StringCopy(pConfig->mHostName, config[kConfigField_Hostname].get().c_str()); - + pConfig->mHostPort = GetPropertyValue(config, kConfigField_Hostport, pConfig->mHostPort); pConfig->mConnectAuto = GetPropertyValue(config, kConfigField_AutoConnect, pConfig->mConnectAuto); pConfig->mDPIScaleEnabled = GetPropertyValue(config, kConfigField_DPIScaleEnabled, pConfig->mDPIScaleEnabled); + pConfig->mBlockTakeover = GetPropertyValue(config, kConfigField_BlockTakeover, pConfig->mBlockTakeover); pConfig->mConfigType = configFileType; pConfig->mReadOnly = !isWritable; } diff --git a/Code/ServerApp/Source/NetImguiServer_Config.h b/Code/ServerApp/Source/NetImguiServer_Config.h index 895a2ac..9421fa8 100644 --- a/Code/ServerApp/Source/NetImguiServer_Config.h +++ b/Code/ServerApp/Source/NetImguiServer_Config.h @@ -20,9 +20,10 @@ class Client static constexpr RuntimeID kInvalidRuntimeID = static_cast(0); enum class eVersion : uint32_t { - Initial = 1, // First version save file deployed - Refresh = 2, // Added refresh rate support - DPIScale = 3, // Added DPI scaling + Initial = 1, // First version save file deployed + Refresh = 2, // Added refresh rate support + DPIScale = 3, // Added DPI scaling + BlockTakeOver = 4, // Added Takeover Block _Count, _Latest = _Count-1 }; @@ -33,24 +34,34 @@ class Client Local2nd, // Config fetched from a second local config file, in the current working directory. Used when 'Local' file is read only Shared, // Config fetched from the shared user folder Transient, // Config created from connection request (command line, OS pipes), cannot be saved + }; + enum class eStatus : uint8_t + { + Disconnected, // No connection detected on client + Connected, // This server is connect to client + Available, // Client already taken, but this server can take over + ErrorBusy, // Client already taken + ErrorVer, // Server/Client network api mismatch }; Client(); Client(const Client& Copy); - char mClientName[128]; //!< Client display name - char mHostName[128]; //!< Client IP or remote host address to attempt connection at - uint32_t mHostPort; //!< Client Port to attempt connection at - RuntimeID mRuntimeID; //!< Unique RuntimeID used to find this Config - bool mConnectAuto; //!< Try automatically connecting to client - bool mConnectRequest; //!< Attempt connecting to Client, after user request - bool mConnected; //!< Associated client is connected to this server - eConfigType mConfigType; //!< Type of the configuration - bool mDPIScaleEnabled; //!< Enable support of Font DPI scaling requests by Server - bool mReadOnly; //!< Config comes from read only file, can't be modified + char mClientName[128]; //!< Client display name + char mHostName[128]; //!< Client IP or remote host address to attempt connection at + uint32_t mHostPort; //!< Client Port to attempt connection at + RuntimeID mRuntimeID; //!< Unique RuntimeID used to find this Config + bool mConnectAuto; //!< Try automatically connecting to client + eConfigType mConfigType; //!< Type of the configuration + bool mDPIScaleEnabled; //!< Enable support of Font DPI scaling requests by Server + bool mBlockTakeover; //!< If another NetImguiServer is allowed to forcefully disconnect this client to connect to it + bool mReadOnly; //!< Config comes from read only file, can't be modified + mutable bool mConnectRequest; //!< Attempt connecting to Client, after user request + mutable bool mConnectForce; //!< Attempt connecting to Client, after user request, even if already connected + mutable eStatus mStatus; //!< Status of associated client inline bool IsReadOnly()const { return mReadOnly; }; inline bool IsTransient()const { return mConfigType == eConfigType::Transient; }; - + inline bool IsConnected()const { return mStatus == eStatus::Connected; } // Add/Edit/Remove config static void SetConfig(const Client& config); //!< Add or replace a client configuration info static void DelConfig(uint32_t configID); //!< Remove a client configuration @@ -59,10 +70,11 @@ class Client static uint32_t GetConfigCount(); // Set property value directly (without having to copy entire structure) - static void SetProperty_Connected(uint32_t configID, bool value); + static bool GetProperty_BlockTakeover(uint32_t configID); + static void SetProperty_Status(uint32_t configID, eStatus Status); static void SetProperty_ConnectAuto(uint32_t configID, bool value); - static void SetProperty_ConnectRequest(uint32_t configID, bool value); - + static void SetProperty_ConnectRequest(uint32_t configID, bool value, bool force); + // Client Config list management static void SaveAll(); static void LoadAll(); diff --git a/Code/ServerApp/Source/NetImguiServer_Network.cpp b/Code/ServerApp/Source/NetImguiServer_Network.cpp index 4627470..3c2b7fb 100644 --- a/Code/ServerApp/Source/NetImguiServer_Network.cpp +++ b/Code/ServerApp/Source/NetImguiServer_Network.cpp @@ -209,7 +209,7 @@ void Communications_ClientExchangeLoop(NetImgui::Internal::Network::SocketInfo* bool bConnected(true); gActiveClientThreadCount++; - NetImguiServer::Config::Client::SetProperty_Connected(pClient->mClientConfigID, true); + NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::Connected); while (bConnected && !gbShutdown && pClient->mbIsConnected && !pClient->mbDisconnectPending ) { bConnected = Communications_Outgoing(pClientSocket, pClient) && @@ -218,7 +218,7 @@ void Communications_ClientExchangeLoop(NetImgui::Internal::Network::SocketInfo* Communications_UpdateClientStats(*pClient); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - NetImguiServer::Config::Client::SetProperty_Connected(pClient->mClientConfigID, false); + NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::Disconnected); NetImgui::Internal::Network::Disconnect(pClientSocket); pClient->Release(); @@ -229,17 +229,32 @@ void Communications_ClientExchangeLoop(NetImgui::Internal::Network::SocketInfo* // Establish connection with Remote Client // Makes sure that Server/Client are compatible //================================================================================================= -bool Communications_InitializeClient(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) +bool Communications_InitializeClient(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient, bool ConnectForce) { NetImgui::Internal::CmdVersion cmdVersionSend; NetImgui::Internal::CmdVersion cmdVersionRcv; NetImgui::Internal::StringCopy(cmdVersionSend.mClientName, "Server"); - - if( NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdVersionSend), cmdVersionSend.mHeader.mSize) && - NetImgui::Internal::Network::DataReceive(pClientSocket, reinterpret_cast(&cmdVersionRcv), cmdVersionRcv.mHeader.mSize) && - cmdVersionRcv.mHeader.mType == NetImgui::Internal::CmdHeader::eCommands::Version && - cmdVersionRcv.mVersion == NetImgui::Internal::CmdVersion::eVersion::_current ) - { + bool ConnectExclusive = NetImguiServer::Config::Client::GetProperty_BlockTakeover(pClient->mClientConfigID); + cmdVersionSend.mFlags |= ConnectExclusive ? static_cast(NetImgui::Internal::CmdVersion::eFlags::ConnectExclusive) : 0; + cmdVersionSend.mFlags |= ConnectForce ? static_cast(NetImgui::Internal::CmdVersion::eFlags::ConnectForce) : 0; + + if( NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdVersionSend), cmdVersionSend.mHeader.mSize) && + NetImgui::Internal::Network::DataReceive(pClientSocket, reinterpret_cast(&cmdVersionRcv), cmdVersionRcv.mHeader.mSize) ) + { + if( cmdVersionRcv.mHeader.mType != NetImgui::Internal::CmdHeader::eCommands::Version || + cmdVersionRcv.mVersion != NetImgui::Internal::CmdVersion::eVersion::_current ) + { + NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::ErrorVer); + return false; + } + else if(cmdVersionRcv.mFlags & static_cast(NetImgui::Internal::CmdVersion::eFlags::IsConnected) ) + { + bool bAvailable = (cmdVersionRcv.mFlags & static_cast(NetImgui::Internal::CmdVersion::eFlags::IsUnavailable)) == 0; + NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, bAvailable ? NetImguiServer::Config::Client::eStatus::Available + : NetImguiServer::Config::Client::eStatus::ErrorBusy); + return false; + } + pClient->Initialize(); pClient->mInfoImguiVerID = cmdVersionRcv.mImguiVerID; pClient->mInfoNetImguiVerID = cmdVersionRcv.mNetImguiVerID; @@ -260,14 +275,14 @@ bool Communications_InitializeClient(NetImgui::Internal::Network::SocketInfo* pC return false; } -void NetworkConnectionNew(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pNewClient) +void NetworkConnectionNew(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pNewClient, bool ConnectForce) { const char* zErrorMsg(nullptr); if (pNewClient == nullptr) { zErrorMsg = "Too many connection on server already"; } - if (zErrorMsg == nullptr && !gbShutdown && Communications_InitializeClient(pClientSocket, pNewClient) == false) { + if (zErrorMsg == nullptr && !gbShutdown && Communications_InitializeClient(pClientSocket, pNewClient, ConnectForce) == false) { zErrorMsg = "Initialization failed. Wrong communication version?"; } @@ -324,7 +339,7 @@ void NetworkConnectRequest_Receive() newClient.mClientConfigID = NetImguiServer::Config::Client::kInvalidRuntimeID; newClient.mClientIndex = freeIndex; NetImguiServer::App::HAL_GetSocketInfo(pClientSocket, newClient.mConnectHost, sizeof(newClient.mConnectHost), newClient.mConnectPort); - NetworkConnectionNew(pClientSocket, &newClient); + NetworkConnectionNew(pClientSocket, &newClient, false); } else NetImgui::Internal::Network::Disconnect(pClientSocket); @@ -351,13 +366,16 @@ void NetworkConnectRequest_Send() NetImgui::Internal::Network::SocketInfo* pClientSocket = nullptr; // Find next client configuration to attempt connection to + bool ConnectForce = false; uint64_t configCount = static_cast(NetImguiServer::Config::Client::GetConfigCount()); uint32_t configIdx = configCount ? static_cast(loopIndex++ % configCount) : 0; if( NetImguiServer::Config::Client::GetConfigByIndex(configIdx, clientConfig) ) { - if( (clientConfig.mConnectAuto || clientConfig.mConnectRequest) && !clientConfig.mConnected && clientConfig.mHostPort != NetImguiServer::Config::Server::sPort) + ConnectForce = clientConfig.mConnectForce; + if( (clientConfig.mConnectAuto || clientConfig.mConnectRequest || clientConfig.mConnectForce) && !clientConfig.IsConnected() && clientConfig.mHostPort != NetImguiServer::Config::Server::sPort) { - NetImguiServer::Config::Client::SetProperty_ConnectRequest(clientConfig.mRuntimeID, false); // Reset the Connection request, we are processing it + NetImguiServer::Config::Client::SetProperty_ConnectRequest(clientConfig.mRuntimeID, false, false); // Reset the Connection request, we are processing it + NetImguiServer::Config::Client::SetProperty_Status(clientConfig.mRuntimeID, NetImguiServer::Config::Client::eStatus::Disconnected); clientConfigID = clientConfig.mRuntimeID; // Keep track of ClientConfig we are attempting to connect to pClientSocket = NetImgui::Internal::Network::Connect(clientConfig.mHostName, clientConfig.mHostPort); } @@ -378,10 +396,10 @@ void NetworkConnectRequest_Send() newClient.mClientIndex = freeIndex; } NetImguiServer::App::HAL_GetSocketInfo(pClientSocket, newClient.mConnectHost, sizeof(newClient.mConnectHost), newClient.mConnectPort); - NetworkConnectionNew(pClientSocket, &newClient); + NetworkConnectionNew(pClientSocket, &newClient, ConnectForce); } } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); } gActiveThreadConnectOut = false; } diff --git a/Code/ServerApp/Source/NetImguiServer_UI.cpp b/Code/ServerApp/Source/NetImguiServer_UI.cpp index 05ca6b7..93bb93d 100644 --- a/Code/ServerApp/Source/NetImguiServer_UI.cpp +++ b/Code/ServerApp/Source/NetImguiServer_UI.cpp @@ -373,6 +373,12 @@ void Popup_ClientConfigEdit() ImGui::SetTooltip("Text content will be scaled up on high resolution monitors for increased readability."); } + // --- Takeover Config --- + ImGui::Checkbox("Block Takeover", &gPopup_ClientConfig_pConfig->mBlockTakeover); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enabled this if you want to prevent other NetImguiServers to forcefully disconnect this client and take over the connection."); + } + // --- Shared Config --- if( NetImguiServer::App::HAL_GetUserSettingFolder() != nullptr ){ bool isShared = gPopup_ClientConfig_pConfig->mConfigType == NetImguiServer::Config::Client::eConfigType::Shared; @@ -383,6 +389,7 @@ void Popup_ClientConfigEdit() ImGui::SetTooltip("Client's settings are saved in 'Working Directory' by default.\nEnabling this option saves their settings in the 'User Directory', making it available no matter which folder the Server Application is launched from."); } } + // --- Save/Cancel --- ImGui::NewLine(); ImGui::Separator(); @@ -655,7 +662,13 @@ void DrawImguiContent_MainMenu_Clients_Entry(RemoteClient::Client* pClient, NetI ImGui::TableSetColumnIndex(0); // Name / Status - ImGui::PushStyleColor(ImGuiCol_CheckMark, pClient && pClient->mbIsConnected ? ImVec4(0.7f, 1.f, 0.25f, 1.f) : ImVec4(1.f, 0.35f, 0.35f, 1.f)); + auto ConfigStatus = pClientConfig ? pClientConfig->mStatus : NetImguiServer::Config::Client::eStatus::Disconnected; + ImVec4 StatusColor = pClient && pClient->mbIsConnected ? ImVec4(0.7f, 1.f, 0.25f, 1.f) + : ConfigStatus == NetImguiServer::Config::Client::eStatus::ErrorVer ? ImVec4(1.f, 0.7f, 0.25f, 1.f) + : ConfigStatus == NetImguiServer::Config::Client::eStatus::ErrorBusy ? ImVec4(1.f, 0.7f, 0.25f, 1.f) + : ConfigStatus == NetImguiServer::Config::Client::eStatus::Available ? ImVec4(1.f, 0.9f, 0.1f, 1.f) + : ImVec4(0.0f, 0.0f, 0.0f, 0.f); + ImGui::PushStyleColor(ImGuiCol_CheckMark, StatusColor); ImGui::BeginDisabled(pClientConfig && pClientConfig->IsReadOnly()); ImGui::RadioButton("##Connected", true); ImGui::PopStyleColor(); @@ -720,8 +733,31 @@ void DrawImguiContent_MainMenu_Clients_Entry(RemoteClient::Client* pClient, NetI if( pClientConfig->IsTransient() ){ ImGui::TextUnformatted("(Request)"); } - else if (!pClientConfig->mConnected && !pClientConfig->mConnectRequest && ImGui::Button("Connect", ImVec2(80 * GetFontDPIScale(),0 )) ){ - NetImguiServer::Config::Client::SetProperty_ConnectRequest(pClientConfig->mRuntimeID, true); + else if (!pClientConfig->IsConnected() && !pClientConfig->mConnectRequest && !pClientConfig->mConnectForce) + { + if( ConfigStatus == NetImguiServer::Config::Client::eStatus::Disconnected ) + { + if( ImGui::Button("Connect", ImVec2(80 * GetFontDPIScale(),0 )) ){ + NetImguiServer::Config::Client::SetProperty_ConnectRequest(pClientConfig->mRuntimeID, true, false); + } + } + else if( ConfigStatus == NetImguiServer::Config::Client::eStatus::Available ) + { + if( ImGui::Button("Takeover", ImVec2(80 * GetFontDPIScale(),0 )) ){ + NetImguiServer::Config::Client::SetProperty_ConnectRequest(pClientConfig->mRuntimeID, true, true); + } + ImGui::SetItemTooltip("Client already connected to another NetImguiServer, disconnect it and force connect this Server"); + } + else if( ConfigStatus == NetImguiServer::Config::Client::eStatus::ErrorBusy ) + { + ImGui::TextUnformatted("(In Use)"); + ImGui::SetItemTooltip("Client already connected to another NetImguiServer and doesn't allow taking over the connection"); + } + else if( ConfigStatus == NetImguiServer::Config::Client::eStatus::ErrorVer ) + { + ImGui::TextUnformatted("(Version)"); + ImGui::SetItemTooltip("Client is using a different network protocol than this NetImguiServer. Update the client code to match the Server."); + } } } } @@ -778,7 +814,7 @@ void DrawImguiContent_MainMenu_Clients() ImGui::PushID("ConfigUnconnected"); for (int i = 0; NetImguiServer::Config::Client::GetConfigByIndex(i, clientConfig); ++i) { - if( !clientConfig.mConnected ){ + if( !clientConfig.IsConnected() ){ ImGui::PushID(i); DrawImguiContent_MainMenu_Clients_Entry(nullptr, &clientConfig); ImGui::PopID();