diff --git a/Code/Client/NetImgui_Api.h b/Code/Client/NetImgui_Api.h index 2e75139..31a2656 100644 --- a/Code/Client/NetImgui_Api.h +++ b/Code/Client/NetImgui_Api.h @@ -4,12 +4,12 @@ //! @Name : NetImgui //================================================================================================= //! @author : Sammy Fatnassi -//! @date : 2024/08/03 -//! @version : v1.11.1 +//! @date : 2024/11/16 +//! @version : v1.11.2 //! @Details : For integration info : https://github.com/sammyfreg/netImgui/wiki //================================================================================================= -#define NETIMGUI_VERSION "1.11.1" // Fix for rare disconnect issue (Github issue #59) -#define NETIMGUI_VERSION_NUM 11101 +#define NETIMGUI_VERSION "1.11.2" // Improved connection speed +#define NETIMGUI_VERSION_NUM 11102 @@ -48,18 +48,19 @@ #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' //------------------------------------------------------------------------------------------------- -#if !defined(NETIMGUI_ENABLED) +#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 diff --git a/Code/Client/Private/NetImgui_Api.cpp b/Code/Client/Private/NetImgui_Api.cpp index b80acef..4f3930a 100644 --- a/Code/Client/Private/NetImgui_Api.cpp +++ b/Code/Client/Private/NetImgui_Api.cpp @@ -98,15 +98,37 @@ void Disconnect(void) { if (!gpClientInfo) return; - // Attempt fake connection on local socket waiting for a Server connection, + // 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.mbDisconnectRequest = true; - if( client.mSocketListenPort != 0 ) + if( client.mpSocketListen.load() != nullptr ) { - Network::SocketInfo* FakeSocket = Network::Connect("127.0.0.1", client.mSocketListenPort); - client.mSocketListenPort = 0; - Network::Disconnect(FakeSocket); + Network::SocketInfo* pFakeSocket(nullptr); + if( client.mSocketListenPort != 0 ) + { + pFakeSocket = Network::Connect("127.0.0.1", client.mSocketListenPort); + client.mSocketListenPort = 0; + } + + if(pFakeSocket) + { + Network::Disconnect(pFakeSocket); + pFakeSocket = nullptr; + } + // If fake connection creation fails, disconnect the listen socket directly + // even though it might potentially cause a race condition + else + { + Network::SocketInfo* pSocket = client.mpSocketListen.exchange(nullptr); + Network::Disconnect(pSocket); + } + } + + if( client.mpSocketPending.load() != nullptr ) + { + Network::Disconnect(client.mpSocketPending); + client.mpSocketPending = nullptr; } } @@ -173,9 +195,16 @@ bool NewFrame(bool bSupportFrameSkip) client.ContextOverride(); } + auto elapsedCheck = std::chrono::steady_clock::now() - client.mLastOutgoingDrawCheckTime; + auto elapsedDraw = std::chrono::steady_clock::now() - client.mLastOutgoingDrawTime; + auto elapsedCheckMs = static_cast(std::chrono::duration_cast(elapsedCheck).count()) / 1000.f; + auto elapsedDrawMs = static_cast(std::chrono::duration_cast(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 = ProcessInputData(client); + client.mbValidDrawFrame = client.mDesiredFps > 0.f && (elapsedDrawMs + elapsedCheckMs*1.25f) > (1000.f/client.mDesiredFps); // Take into account delay until next method call, for more precise fps + ProcessInputData(client); // We are about to start drawing for remote context, check for font data update const ImFontAtlas* pFonts = ImGui::GetIO().Fonts; @@ -193,10 +222,7 @@ bool NewFrame(bool bSupportFrameSkip) assert(client.mbFontUploaded); // Update current active content with our time - const auto TimeNow = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsedSec = TimeNow - client.mTimeTracking; - ImGui::GetIO().DeltaTime = std::max(1.f / 1000.f, elapsedSec.count()); - client.mTimeTracking = TimeNow; + ImGui::GetIO().DeltaTime = std::max(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 ) diff --git a/Code/Client/Private/NetImgui_Client.cpp b/Code/Client/Private/NetImgui_Client.cpp index 0aa522c..9816fa5 100644 --- a/Code/Client/Private/NetImgui_Client.cpp +++ b/Code/Client/Private/NetImgui_Client.cpp @@ -86,8 +86,9 @@ void Communications_Incoming_Input(ClientInfo& client, uint8_t*& pCmdData) auto pCmdInput = reinterpret_cast(pCmdData); pCmdData = nullptr; // Take ownership of the data, prevent Free size_t keyCount(pCmdInput->mKeyCharCount); + client.mDesiredFps = pCmdInput->mDesiredFps > 0.f ? pCmdInput->mDesiredFps : 0.f; client.mPendingKeyIn.AddData(pCmdInput->mKeyChars, keyCount); - client.mPendingInputIn.Assign(pCmdInput); + client.mPendingInputIn.Assign(pCmdInput); } } @@ -110,12 +111,12 @@ void Communications_Incoming_Clipboard(ClientInfo& client, uint8_t*& pCmdData) // OUTCOM: TEXTURE // Transmit all pending new/updated texture //================================================================================================= -bool Communications_Outgoing_Textures(ClientInfo& client) +void Communications_Outgoing_Textures(ClientInfo& client) { - bool bSuccess(true); client.ProcessTexturePending(); if( client.mbHasTextureUpdate ) { + bool bSuccess(true); for(auto& cmdTexture : client.mTextures) { if( !cmdTexture.mbSent && cmdTexture.mpCmdTexture ) @@ -128,36 +129,32 @@ bool Communications_Outgoing_Textures(ClientInfo& client) } client.mbHasTextureUpdate = !bSuccess; } - return bSuccess; } //================================================================================================= // OUTCOM: BACKGROUND // Transmit the current client background settings //================================================================================================= -bool Communications_Outgoing_Background(ClientInfo& client) +void Communications_Outgoing_Background(ClientInfo& client) { - bool bSuccess(true); CmdBackground* pPendingBackground = client.mPendingBackgroundOut.Release(); if( pPendingBackground ) { - bSuccess = Network::DataSend(client.mpSocketComs, pPendingBackground, pPendingBackground->mHeader.mSize); + Network::DataSend(client.mpSocketComs, pPendingBackground, pPendingBackground->mHeader.mSize); netImguiDeleteSafe(pPendingBackground); } - return bSuccess; } //================================================================================================= // OUTCOM: FRAME // Transmit a new dearImgui frame to render //================================================================================================= -bool Communications_Outgoing_Frame(ClientInfo& client) +void Communications_Outgoing_Frame(ClientInfo& client) { - bool bSuccess(true); CmdDrawFrame* pPendingDraw = client.mPendingFrameOut.Release(); if( pPendingDraw ) { - pPendingDraw->mFrameIndex = client.mFrameIndex++; + pPendingDraw->mFrameIndex = client.mFrameIndex++; //--------------------------------------------------------------------- // Apply delta compression to DrawCommand, when requested if( pPendingDraw->mCompressed ) @@ -181,7 +178,10 @@ bool Communications_Outgoing_Frame(ClientInfo& client) //--------------------------------------------------------------------- // Send Command to server pPendingDraw->ToOffsets(); - bSuccess = Network::DataSend(client.mpSocketComs, pPendingDraw, pPendingDraw->mHeader.mSize); + if( Network::DataSend(client.mpSocketComs, pPendingDraw, pPendingDraw->mHeader.mSize) ) + { + client.mLastOutgoingDrawTime = std::chrono::steady_clock::now(); + } //--------------------------------------------------------------------- // Free created data once sent (when not used in next frame) @@ -189,117 +189,94 @@ bool Communications_Outgoing_Frame(ClientInfo& client) netImguiDeleteSafe(pPendingDraw); } } - return bSuccess; } //================================================================================================= // OUTCOM: DISCONNECT // Signal that we will be disconnecting //================================================================================================= -bool Communications_Outgoing_Disconnect(ClientInfo& client) +void Communications_Outgoing_Disconnect(ClientInfo& client) { if( client.mbDisconnectRequest ) { CmdDisconnect cmdDisconnect; Network::DataSend(client.mpSocketComs, &cmdDisconnect, cmdDisconnect.mHeader.mSize); - return false; + client.mbDisconnectProcessed = true; } - return true; -} - -//================================================================================================= -// OUTCOM: PING -// Signal end of outgoing communications and still alive -//================================================================================================= -bool Communications_Outgoing_Ping(ClientInfo& client) -{ - CmdPing cmdPing; - return Network::DataSend(client.mpSocketComs, &cmdPing, cmdPing.mHeader.mSize); } //================================================================================================= // OUTCOM: Clipboard // Send client 'Copy' clipboard content to Server //================================================================================================= -bool Communications_Outgoing_Clipboard(ClientInfo& client) +void Communications_Outgoing_Clipboard(ClientInfo& client) { - bool bResult(true); CmdClipboard* pPendingClipboard = client.mPendingClipboardOut.Release(); if( pPendingClipboard ){ pPendingClipboard->ToOffsets(); - bResult = Network::DataSend(client.mpSocketComs, pPendingClipboard, pPendingClipboard->mHeader.mSize); + Network::DataSend(client.mpSocketComs, pPendingClipboard, pPendingClipboard->mHeader.mSize); netImguiDeleteSafe(pPendingClipboard); - } - return bResult; } //================================================================================================= // INCOMING COMMUNICATIONS //================================================================================================= -bool Communications_Incoming(ClientInfo& client) +void Communications_Incoming(ClientInfo& client) { - bool bOk(true); - bool bPingReceived(false); - while( bOk && !bPingReceived ) + if( Network::DataReceivePending(client.mpSocketComs) ) { CmdHeader cmdHeader; - uint8_t* pCmdData = nullptr; - bOk = Network::DataReceive(client.mpSocketComs, &cmdHeader, sizeof(cmdHeader)); - if( bOk && cmdHeader.mSize > sizeof(CmdHeader) ) + uint8_t* pCmdData(nullptr); + bool bCmdRcv = Network::DataReceive(client.mpSocketComs, &cmdHeader, sizeof(cmdHeader)); + bCmdRcv &= cmdHeader.mType < NetImgui::Internal::CmdHeader::eCommands::_Invalid; + + if( bCmdRcv && cmdHeader.mSize > sizeof(CmdHeader) ) { pCmdData = netImguiSizedNew(cmdHeader.mSize); *reinterpret_cast(pCmdData) = cmdHeader; - bOk = Network::DataReceive(client.mpSocketComs, &pCmdData[sizeof(cmdHeader)], cmdHeader.mSize-sizeof(cmdHeader)); + bCmdRcv = Network::DataReceive(client.mpSocketComs, &pCmdData[sizeof(cmdHeader)], cmdHeader.mSize-sizeof(cmdHeader)); } - - if( bOk ) + + if( bCmdRcv ) { switch( cmdHeader.mType ) { - case CmdHeader::eCommands::Ping: bPingReceived = true; break; - case CmdHeader::eCommands::Disconnect: bOk = false; break; - case CmdHeader::eCommands::Input: Communications_Incoming_Input(client, pCmdData); break; - case CmdHeader::eCommands::Clipboard: Communications_Incoming_Clipboard(client, pCmdData); break; - // Commands not received in main loop, by Client - case CmdHeader::eCommands::Invalid: - case CmdHeader::eCommands::Version: - case CmdHeader::eCommands::Texture: - case CmdHeader::eCommands::DrawFrame: - case CmdHeader::eCommands::Background: break; + case CmdHeader::eCommands::Disconnect: client.mbDisconnectProcessed = true; break; + case CmdHeader::eCommands::Input: Communications_Incoming_Input(client, pCmdData); break; + case CmdHeader::eCommands::Clipboard: Communications_Incoming_Clipboard(client, pCmdData); 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::_Invalid: break; } - } + } + else + { + client.mbDisconnectRequest = true; + } netImguiDeleteSafe(pCmdData); } - return bOk; + else + { + // Prevent high CPU usage when waiting for new data + //std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(250)); + } } //================================================================================================= // OUTGOING COMMUNICATIONS //================================================================================================= -bool Communications_Outgoing(ClientInfo& client) +void Communications_Outgoing(ClientInfo& client) { - bool bSuccess(true); - if( bSuccess ){ - bSuccess = Communications_Outgoing_Textures(client); - } - if( bSuccess ){ - bSuccess = Communications_Outgoing_Background(client); - } - if( bSuccess ){ - bSuccess = Communications_Outgoing_Clipboard(client); - } - if( bSuccess ){ - bSuccess = Communications_Outgoing_Frame(client); - } - if( bSuccess ){ - bSuccess = Communications_Outgoing_Disconnect(client); - } - if( bSuccess ){ - bSuccess = Communications_Outgoing_Ping(client); // Always finish with a ping - } - - return bSuccess; + Communications_Outgoing_Textures(client); + Communications_Outgoing_Background(client); + Communications_Outgoing_Clipboard(client); + Communications_Outgoing_Frame(client); + Communications_Outgoing_Disconnect(client); } //================================================================================================= @@ -309,19 +286,30 @@ bool Communications_Outgoing(ClientInfo& client) bool Communications_Initialize(ClientInfo& client) { CmdVersion cmdVersionSend, cmdVersionRcv; - bool bResultRcv = Network::DataReceive(client.mpSocketPending, &cmdVersionRcv, sizeof(cmdVersionRcv)); + + //--------------------------------------------------------------------- + // Handshake confirming connection validity + //--------------------------------------------------------------------- + while( !Network::DataReceivePending(client.mpSocketPending) ) + { + std::this_thread::yield(); // Idle until we receive the remote data + } + 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); + 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); + //--------------------------------------------------------------------- + // Connection established, init client + //--------------------------------------------------------------------- if(bCanConnect && bResultSend) { Network::SocketInfo* pNewConnect = client.mpSocketPending.exchange(nullptr); @@ -359,15 +347,14 @@ void Communications_Loop(void* pClientVoid) { IM_ASSERT(pClientVoid != nullptr); ClientInfo* pClient = reinterpret_cast(pClientVoid); - bool bConnected = pClient->IsConnected(); pClient->mbDisconnectRequest = false; + pClient->mbDisconnectProcessed = false; pClient->mbClientThreadActive = true; - while( bConnected && !pClient->mbDisconnectRequest ) + while( !pClient->mbDisconnectProcessed ) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - //std::this_thread::yield(); - bConnected = Communications_Outgoing(*pClient) && Communications_Incoming(*pClient); + Communications_Outgoing(*pClient); + Communications_Incoming(*pClient); } pClient->KillSocketComs(); @@ -403,13 +390,11 @@ void CommunicationsHost(void* pClientVoid) while( pClient->mpSocketListen.load() != nullptr && !pClient->mbDisconnectRequest ) { pClient->mpSocketPending = Network::ListenConnect(pClient->mpSocketListen); - if( !pClient->mbDisconnectRequest && - pClient->mpSocketPending.load() != nullptr && - Communications_Initialize(*pClient) ) + 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 + 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); @@ -510,7 +495,7 @@ void ClientInfo::ContextOverride() // Keep a copy of original settings of this context mSavedContextValues.Save(mpContext); - mTimeTracking = std::chrono::high_resolution_clock::now(); + mLastOutgoingDrawCheckTime = std::chrono::high_resolution_clock::now(); // Override some settings // Note: Make sure every setting overwritten here, are handled in 'SavedImguiContext::Save(...)' diff --git a/Code/Client/Private/NetImgui_Client.h b/Code/Client/Private/NetImgui_Client.h index 47c3a6c..55a00ab 100644 --- a/Code/Client/Private/NetImgui_Client.h +++ b/Code/Client/Private/NetImgui_Client.h @@ -54,15 +54,15 @@ struct ClientInfo { using VecTexture = ImVector; using BufferKeys = Ringbuffer; - using Time = std::chrono::time_point; + using TimePoint = std::chrono::time_point; 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; + 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(); @@ -79,7 +79,7 @@ struct ClientInfo 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]; + CmdTexture* mTexturesPending[16] = {}; ExchangePtr mPendingFrameOut; ExchangePtr mPendingBackgroundOut; ExchangePtr mPendingInputIn; @@ -92,15 +92,17 @@ struct ClientInfo 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; - Time mTimeTracking; // Used to update Dear ImGui time delta on remote context std::atomic_uint32_t mTexturesPendingSent; std::atomic_uint32_t mTexturesPendingCreated; - + bool mbDisconnectRequest = false; // Waiting to Disconnect + bool mbDisconnectProcessed = false; // Disconnect command sent to server, ready to disconnect bool mbClientThreadActive = false; bool mbListenThreadActive = false; bool mbHasTextureUpdate = false; @@ -118,6 +120,7 @@ struct ClientInfo 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; diff --git a/Code/Client/Private/NetImgui_CmdPackets.h b/Code/Client/Private/NetImgui_CmdPackets.h index c4de027..99377df 100644 --- a/Code/Client/Private/NetImgui_CmdPackets.h +++ b/Code/Client/Private/NetImgui_CmdPackets.h @@ -8,19 +8,14 @@ namespace NetImgui { namespace Internal //Note: If updating any of these commands data structure, increase 'CmdVersion::eVersion' -struct CmdHeader +struct alignas(8) CmdHeader { - enum class eCommands : uint8_t { Invalid, Ping, Disconnect, Version, Texture, Input, DrawFrame, Background, Clipboard }; + enum class eCommands : uint8_t { Disconnect, Version, Texture, Input, DrawFrame, Background, Clipboard, _Invalid }; CmdHeader(){} CmdHeader(eCommands CmdType, uint16_t Size) : mSize(Size), mType(CmdType){} uint32_t mSize = 0; - eCommands mType = eCommands::Invalid; - uint8_t mPadding[3] = {0,0,0}; -}; - -struct alignas(8) CmdPing -{ - CmdHeader mHeader = CmdHeader(CmdHeader::eCommands::Ping, sizeof(CmdPing)); + eCommands mType = eCommands::_Invalid; + uint8_t mPadding[3] = {}; }; struct alignas(8) CmdDisconnect @@ -47,6 +42,7 @@ struct alignas(8) CmdVersion 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 // Insert new version here //-------------------------------- @@ -61,10 +57,10 @@ struct alignas(8) CmdVersion 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] = {}; 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(sizeof(ImWchar)); @@ -202,6 +198,7 @@ struct alignas(8) CmdInput 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] = {}; diff --git a/Code/Client/Private/NetImgui_Network.h b/Code/Client/Private/NetImgui_Network.h index 1d5335e..e952593 100644 --- a/Code/Client/Private/NetImgui_Network.h +++ b/Code/Client/Private/NetImgui_Network.h @@ -5,15 +5,16 @@ namespace NetImgui { namespace Internal { namespace Network struct SocketInfo; -bool Startup (void); -void Shutdown (void); +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); +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 DataReceive (SocketInfo* pClientSocket, void* pDataIn, size_t Size); -bool DataSend (SocketInfo* pClientSocket, void* pDataOut, size_t Size); +bool DataReceivePending (SocketInfo* pClientSocket); // True if some new data if waiting to be processed from remote connection +bool DataReceive (SocketInfo* pClientSocket, void* pDataIn, size_t Size); // Read X amount of bytes to remote connection. Will idle until size request is fullfilled. +bool DataSend (SocketInfo* pClientSocket, void* pDataOut, size_t Size); // Send x amount of bytes to remote connection. Will idle until size request is fullfilled. }}} //namespace NetImgui::Internal::Network diff --git a/Code/Client/Private/NetImgui_NetworkUE4.cpp b/Code/Client/Private/NetImgui_NetworkUE4.cpp index b9be3ea..edd0577 100644 --- a/Code/Client/Private/NetImgui_NetworkUE4.cpp +++ b/Code/Client/Private/NetImgui_NetworkUE4.cpp @@ -17,10 +17,26 @@ namespace NetImgui { namespace Internal { namespace Network { +//================================================================================================= +// Wrapper around native socket object and init some socket options +//================================================================================================= struct SocketInfo { - SocketInfo(FSocket* pSocket) : mpSocket(pSocket) {} - ~SocketInfo() { Close(); } + SocketInfo(FSocket* pSocket) + : mpSocket(pSocket) + { + if( mpSocket ) + { + mpSocket->SetNonBlocking(true); + mpSocket->SetNoDelay(true); + } + } + + ~SocketInfo() + { + Close(); + } + void Close() { if(mpSocket ) @@ -30,7 +46,7 @@ struct SocketInfo mpSocket = nullptr; } } - FSocket* mpSocket; + FSocket* mpSocket = nullptr; }; bool Startup() @@ -42,28 +58,32 @@ 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(); - auto ResolveInfo = SocketSubSystem->GetHostByName(ServerHost); - while( !ResolveInfo->IsComplete() ){ + SocketInfo* pSocketInfo = nullptr; + ISocketSubsystem* SocketSubSystem = ISocketSubsystem::Get(); + auto ResolveInfo = SocketSubSystem->GetHostByName(ServerHost); + + while( ResolveInfo && !ResolveInfo->IsComplete() ){ FPlatformProcess::YieldThread(); } - if (ResolveInfo->GetErrorCode() == 0) + if ( ResolveInfo && ResolveInfo->GetErrorCode() == 0) { TSharedRef IpAddress = ResolveInfo->GetResolvedAddress().Clone(); IpAddress->SetPort(ServerPort); if (IpAddress->IsValid()) { - FSocket* pNewSocket = SocketSubSystem->CreateSocket(NAME_Stream, "netImgui", IpAddress->GetProtocolType()); + FSocket* pNewSocket = SocketSubSystem->CreateSocket(NAME_Stream, "NetImgui", IpAddress->GetProtocolType()); if (pNewSocket) { - pNewSocket->SetNonBlocking(false); - pSocketInfo = netImguiNew(pNewSocket); + pNewSocket->SetNonBlocking(true); if (pNewSocket->Connect(IpAddress.Get())) { + pSocketInfo = netImguiNew(pNewSocket); return pSocketInfo; } } @@ -73,13 +93,16 @@ SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) return nullptr; } +//================================================================================================= +// Start waiting for connection request on this socket +//================================================================================================= SocketInfo* ListenStart(uint32_t ListenPort) { ISocketSubsystem* PlatformSocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); TSharedPtr IpAddress = PlatformSocketSub->GetLocalBindAddr(*GLog); IpAddress->SetPort(ListenPort); - FSocket* pNewListenSocket = PlatformSocketSub->CreateSocket(NAME_Stream, "netImguiListen", IpAddress->GetProtocolType()); + FSocket* pNewListenSocket = PlatformSocketSub->CreateSocket(NAME_Stream, "NetImguiListen", IpAddress->GetProtocolType()); if( pNewListenSocket ) { SocketInfo* pListenSocketInfo = netImguiNew(pNewListenSocket); @@ -100,14 +123,16 @@ SocketInfo* ListenStart(uint32_t ListenPort) return nullptr; } +//================================================================================================= +// Establish a new connection to a remote request +//================================================================================================= SocketInfo* ListenConnect(SocketInfo* pListenSocket) { if (pListenSocket) { - FSocket* pNewSocket = pListenSocket->mpSocket->Accept(FString("netImgui")); + FSocket* pNewSocket = pListenSocket->mpSocket->Accept(FString("NetImgui")); if( pNewSocket ) { - pNewSocket->SetNonBlocking(false); SocketInfo* pSocketInfo = netImguiNew(pNewSocket); return pSocketInfo; } @@ -115,23 +140,70 @@ SocketInfo* ListenConnect(SocketInfo* pListenSocket) return nullptr; } +//================================================================================================= +// Close a connection and free allocated object +//================================================================================================= void Disconnect(SocketInfo* pClientSocket) { - netImguiDelete(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->mpSocket->HasPendingData(PendingDataSize) || (pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected); } +//================================================================================================= +// Block until all requested data has been received from the remote connection +//================================================================================================= bool DataReceive(SocketInfo* pClientSocket, void* pDataIn, size_t Size) { - int32 sizeRcv(0); - bool bResult = pClientSocket->mpSocket->Recv(reinterpret_cast(pDataIn), Size, sizeRcv, ESocketReceiveFlags::WaitAll); - return bResult && static_cast(Size) == sizeRcv; + int32 totalRcv(0), sizeRcv(0); + while( totalRcv < static_cast(Size) ) + { + if( pClientSocket->mpSocket->Recv(&reinterpret_cast(pDataIn)[totalRcv], static_cast(Size)-totalRcv, sizeRcv, ESocketReceiveFlags::None) ) + { + totalRcv += sizeRcv; + } + else + { + if( pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected ) + { + return false; // Connection error, abort transmission + } + std::this_thread::yield(); + } + } + return totalRcv == static_cast(Size); } +//================================================================================================= +// Block until all requested data has been sent to remote connection +//================================================================================================= bool DataSend(SocketInfo* pClientSocket, void* pDataOut, size_t Size) { - int32 sizeSent(0); - bool bResult = pClientSocket->mpSocket->Send(reinterpret_cast(pDataOut), Size, sizeSent); - return bResult && static_cast(Size) == sizeSent; + int32 totalSent(0), sizeSent(0); + while( totalSent < static_cast(Size) ) + { + if( pClientSocket->mpSocket->Send(&reinterpret_cast(pDataOut)[totalSent], Size-totalSent, sizeSent) ) + { + totalSent += sizeSent; + } + else + { + if( pClientSocket->mpSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected ) + { + return false; // Connection error, abort transmission + } + std::this_thread::yield(); + } + } + return totalSent == static_cast(Size); } }}} // namespace NetImgui::Internal::Network @@ -142,5 +214,4 @@ bool DataSend(SocketInfo* pClientSocket, void* pDataOut, size_t Size) extern int sSuppresstLNK4221_NetImgui_NetworkUE4; int sSuppresstLNK4221_NetImgui_NetworkUE4(0); - #endif // #if NETIMGUI_ENABLED && defined(__UNREAL__) diff --git a/Code/Client/Private/NetImgui_NetworkWin32.cpp b/Code/Client/Private/NetImgui_NetworkWin32.cpp index a4e8857..6627699 100644 --- a/Code/Client/Private/NetImgui_NetworkWin32.cpp +++ b/Code/Client/Private/NetImgui_NetworkWin32.cpp @@ -12,10 +12,33 @@ namespace NetImgui { namespace Internal { namespace Network { - +//================================================================================================= +// Wrapper around native socket object and init some socket options +//================================================================================================= struct SocketInfo { - SocketInfo(SOCKET socket) : mSocket(socket){} + SocketInfo(SOCKET socket) + : mSocket(socket) + { + u_long kNonBlocking = true; + ioctlsocket(mSocket, static_cast(FIONBIO), &kNonBlocking); + + constexpr DWORD kComsNoDelay = 1; + setsockopt(mSocket, SOL_SOCKET, TCP_NODELAY, reinterpret_cast(&kComsNoDelay), sizeof(kComsNoDelay)); + + //constexpr int kComsSendBuffer = 1014*1024; + //setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&kComsSendBuffer), sizeof(kComsSendBuffer)); + + //constexpr int kComsRcvBuffer = 1014*1024; + //setsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&kComsRcvBuffer), sizeof(kComsRcvBuffer)); + + #if 0 // @sammyfreg : No timeout useful when debugging, to keep connection alive while code breakpoint + constexpr DWORD kComsTimeoutMs = 10000; + setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&kComsTimeoutMs), sizeof(kComsTimeoutMs)); + setsockopt(mSocket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&kComsTimeoutMs), sizeof(kComsTimeoutMs)); + #endif + } + SOCKET mSocket; }; @@ -33,12 +56,9 @@ void Shutdown() WSACleanup(); } -inline void SetNonBlocking(SOCKET Socket, bool bIsNonBlocking) -{ - u_long IsNonBlocking = bIsNonBlocking; - ioctlsocket(Socket, static_cast(FIONBIO), &IsNonBlocking); -} - +//================================================================================================= +// Try establishing a connection to a remote client at given address +//================================================================================================= SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) { SOCKET ClientSocket = socket(AF_INET , SOCK_STREAM , 0); @@ -54,8 +74,7 @@ SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) while( pResultCur && !pSocketInfo ) { if( connect(ClientSocket, pResultCur->ai_addr, static_cast(pResultCur->ai_addrlen)) == 0 ) - { - SetNonBlocking(ClientSocket, false); + { pSocketInfo = netImguiNew(ClientSocket); } pResultCur = pResultCur->ai_next; @@ -68,6 +87,9 @@ SocketInfo* Connect(const char* ServerHost, uint32_t ServerPort) return pSocketInfo; } +//================================================================================================= +// Start waiting for connection request on this socket +//================================================================================================= SocketInfo* ListenStart(uint32_t ListenPort) { SOCKET ListenSocket = INVALID_SOCKET; @@ -85,7 +107,8 @@ SocketInfo* ListenStart(uint32_t ListenPort) if( bind(ListenSocket, reinterpret_cast(&server), sizeof(server)) != SOCKET_ERROR && listen(ListenSocket, 0) != SOCKET_ERROR ) { - SetNonBlocking(ListenSocket, false); + u_long kIsNonBlocking = false; + ioctlsocket(ListenSocket, static_cast(FIONBIO), &kIsNonBlocking); return netImguiNew(ListenSocket); } closesocket(ListenSocket); @@ -93,6 +116,9 @@ SocketInfo* ListenStart(uint32_t ListenPort) return nullptr; } +//================================================================================================= +// Establish a new connection to a remote request +//================================================================================================= SocketInfo* ListenConnect(SocketInfo* ListenSocket) { if( ListenSocket ) @@ -102,18 +128,15 @@ SocketInfo* ListenConnect(SocketInfo* ListenSocket) SOCKET ClientSocket = accept(ListenSocket->mSocket, &ClientAddress, &Size) ; if (ClientSocket != INVALID_SOCKET) { - #if 0 // @sammyfreg : No timeout useful when debugging, to keep connection alive while code breakpoint - static constexpr DWORD kComsTimeoutMs = 2000; - setsockopt(ClientSocket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&kComsTimeoutMs), sizeof(kComsTimeoutMs)); - setsockopt(ClientSocket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&kComsTimeoutMs), sizeof(kComsTimeoutMs)); - #endif - SetNonBlocking(ClientSocket, false); return netImguiNew(ClientSocket); } } return nullptr; } +//================================================================================================= +// Close a connection and free allocated object +//================================================================================================= void Disconnect(SocketInfo* pClientSocket) { if( pClientSocket ) @@ -124,16 +147,65 @@ void Disconnect(SocketInfo* pClientSocket) } } +//================================================================================================= +// Return trie if data has been received, or there's a connection error +//================================================================================================= +bool DataReceivePending(SocketInfo* pClientSocket) +{ + char Unused[4]; + int resultRcv = recv(pClientSocket->mSocket, Unused, 1, MSG_PEEK); + // Note: return true on a connection error, to exit code looping on the data wait + return resultRcv > 0 || (resultRcv == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK); +} + +//================================================================================================= +// Block until all requested data has been received from the remote connection +//================================================================================================= bool DataReceive(SocketInfo* pClientSocket, void* pDataIn, size_t Size) { - int resultRcv = recv(pClientSocket->mSocket, reinterpret_cast(pDataIn), static_cast(Size), MSG_WAITALL); - return resultRcv != SOCKET_ERROR && static_cast(Size) == resultRcv; + int totalRcv(0); + while( totalRcv < static_cast(Size) ) + { + int resultRcv = recv(pClientSocket->mSocket, &reinterpret_cast(pDataIn)[totalRcv], static_cast(Size)-totalRcv, 0); + if( resultRcv != SOCKET_ERROR ) + { + totalRcv += resultRcv; + } + else + { + if( WSAGetLastError() != WSAEWOULDBLOCK ) + { + return false; // Connection error, abort transmission + } + std::this_thread::yield(); + } + } + return totalRcv == static_cast(Size); } +//================================================================================================= +// Block until all requested data has been sent to remote connection +//================================================================================================= bool DataSend(SocketInfo* pClientSocket, void* pDataOut, size_t Size) { - int resultSend = send(pClientSocket->mSocket, reinterpret_cast(pDataOut), static_cast(Size), 0); - return resultSend != SOCKET_ERROR && static_cast(Size) == resultSend; + int totalSent(0); + while( totalSent < static_cast(Size) ) + { + int resultSent = send(pClientSocket->mSocket, &reinterpret_cast(pDataOut)[totalSent], static_cast(Size)-totalSent, 0); + if( resultSent != SOCKET_ERROR ) + { + totalSent += resultSent; + } + else + { + if( WSAGetLastError() != WSAEWOULDBLOCK ) + { + return false; // Connection error, abort transmission + } + std::this_thread::yield(); + } + } + return totalSent == static_cast(Size); } }}} // namespace NetImgui::Internal::Network diff --git a/Code/Client/Private/NetImgui_Shared.h b/Code/Client/Private/NetImgui_Shared.h index c368e53..f970e0e 100644 --- a/Code/Client/Private/NetImgui_Shared.h +++ b/Code/Client/Private/NetImgui_Shared.h @@ -94,7 +94,7 @@ class ExchangePtr inline TType* Release(); inline void Assign(TType*& pNewData); inline void Free(); - inline bool IsNull()const { return mpData.load() != nullptr; } + inline bool IsNull()const { return mpData.load() == nullptr; } private: std::atomic mpData; diff --git a/Code/Sample/SampleDisabled/SampleDisabled.cpp b/Code/Sample/SampleDisabled/SampleDisabled.cpp index cf12c1f..6890bc6 100644 --- a/Code/Sample/SampleDisabled/SampleDisabled.cpp +++ b/Code/Sample/SampleDisabled/SampleDisabled.cpp @@ -7,7 +7,10 @@ //================================================================================================= #include -#include "imgui.h" // Since NetImgui is disabled in this sample, it will not include this header +// Since NetImgui is disabled in this sample, NetImgui_Api.h will not include this header, +// so must include it manually +#include "imgui.h" + #include "../Common/Sample.h" //================================================================================================= diff --git a/Code/ServerApp/Source/Custom/NetImguiServer_App_Custom.cpp b/Code/ServerApp/Source/Custom/NetImguiServer_App_Custom.cpp index fb62d4e..cbbaf55 100644 --- a/Code/ServerApp/Source/Custom/NetImguiServer_App_Custom.cpp +++ b/Code/ServerApp/Source/Custom/NetImguiServer_App_Custom.cpp @@ -67,8 +67,8 @@ bool CreateTexture_Custom( ServerTexture& serverTexture, const NetImgui::Interna { ImColor colorStart = pCustomData1->m_ColorStart; ImColor colorEnd = pCustomData1->m_ColorEnd; - uint32_t* pTempData = static_cast(malloc(cmdTexture.mWidth*cmdTexture.mHeight*sizeof(uint32_t))); - for(uint32_t x=0; x(malloc((size_t)cmdTexture.mWidth*(size_t)cmdTexture.mHeight*sizeof(uint32_t))); + for(uint32_t x=0; pTempData && x(x) / static_cast(cmdTexture.mWidth); ImColor colorCurrent = ImColor( colorStart.Value.x * ratio + colorEnd.Value.x * (1.f - ratio), colorStart.Value.y * ratio + colorEnd.Value.y * (1.f - ratio), @@ -92,7 +92,7 @@ bool CreateTexture_Custom( ServerTexture& serverTexture, const NetImgui::Interna const customTextureData2* pCustomData2 = reinterpret_cast(cmdTexture.mpTextureData.Get()); if( customDataSize == sizeof(customTextureData2) && pCustomData2->m_Stamp == customTextureData2::kStamp ) { - uint32_t* pTempData = static_cast(malloc(cmdTexture.mWidth*cmdTexture.mHeight*sizeof(uint32_t))); + uint32_t* pTempData = static_cast(malloc((size_t)cmdTexture.mWidth*(size_t)cmdTexture.mHeight*sizeof(uint32_t))); for(uint32_t x=0; pTempData && x(pCmdData); - pCmdData = nullptr; // Take ownership of the data, preventing freeing + auto pCmdDraw = reinterpret_cast(pCmdData); + pCmdData = nullptr; // Take ownership of the data, preventing freeing pCmdDraw->ToPointers(); pClient->ReceiveDrawFrame(pCmdDraw); } @@ -77,98 +77,95 @@ void Communications_Incoming_CmdClipboard(RemoteClient::Client* pClient, uint8_t // Receive every commands sent by remote client and process them // We keep receiving until we detect a ping command (signal end of commands) //================================================================================================= -bool Communications_Incoming(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) +void Communications_Incoming(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) { - bool bOk(true); - bool bPingReceived(false); - NetImgui::Internal::CmdHeader cmdHeader; - uint64_t frameDataReceived(0); - while( bOk && !bPingReceived ) - { + if( NetImgui::Internal::Network::DataReceivePending(pClientSocket) ) + { + bool bOk(true); + NetImgui::Internal::CmdHeader cmdHeader; //--------------------------------------------------------------------- // Receive all of the data from client, // and allocate memory to receive it if needed //--------------------------------------------------------------------- uint8_t* pCmdData = nullptr; bOk = NetImgui::Internal::Network::DataReceive(pClientSocket, &cmdHeader, sizeof(cmdHeader)); - frameDataReceived += sizeof(cmdHeader); + bOk &= cmdHeader.mType < NetImgui::Internal::CmdHeader::eCommands::_Invalid; if( bOk && cmdHeader.mSize > sizeof(cmdHeader) ) { pCmdData = NetImgui::Internal::netImguiSizedNew(cmdHeader.mSize); *reinterpret_cast(pCmdData) = cmdHeader; char* pDataRemaining = reinterpret_cast(&pCmdData[sizeof(cmdHeader)]); - const size_t sizeToRead = cmdHeader.mSize - sizeof(cmdHeader); - bOk = NetImgui::Internal::Network::DataReceive(pClientSocket, pDataRemaining, sizeToRead); - frameDataReceived += sizeToRead; + bOk = NetImgui::Internal::Network::DataReceive(pClientSocket, pDataRemaining, cmdHeader.mSize - sizeof(cmdHeader)); } - + //--------------------------------------------------------------------- // Process the command type //--------------------------------------------------------------------- if( bOk ) { + pClient->mStatsDataRcvd += cmdHeader.mSize; + pClient->mLastIncomingComTime = std::chrono::steady_clock::now(); switch( cmdHeader.mType ) { - case NetImgui::Internal::CmdHeader::eCommands::Ping: bPingReceived = true; break; - case NetImgui::Internal::CmdHeader::eCommands::Disconnect: bOk = false; break; - case NetImgui::Internal::CmdHeader::eCommands::Texture: Communications_Incoming_CmdTexture(pClient, pCmdData); break; - case NetImgui::Internal::CmdHeader::eCommands::Background: Communications_Incoming_CmdBackground(pClient, pCmdData); break; - case NetImgui::Internal::CmdHeader::eCommands::DrawFrame: Communications_Incoming_CmdDrawFrame(pClient, pCmdData); break; - case NetImgui::Internal::CmdHeader::eCommands::Clipboard: Communications_Incoming_CmdClipboard(pClient, pCmdData); break; - // Commands not received in main loop, by Server - case NetImgui::Internal::CmdHeader::eCommands::Invalid: - case NetImgui::Internal::CmdHeader::eCommands::Version: - case NetImgui::Internal::CmdHeader::eCommands::Input: break; + case NetImgui::Internal::CmdHeader::eCommands::Disconnect: pClient->mbIsConnected = false; break; + case NetImgui::Internal::CmdHeader::eCommands::Texture: Communications_Incoming_CmdTexture(pClient, pCmdData); break; + case NetImgui::Internal::CmdHeader::eCommands::Background: Communications_Incoming_CmdBackground(pClient, pCmdData); break; + case NetImgui::Internal::CmdHeader::eCommands::DrawFrame: Communications_Incoming_CmdDrawFrame(pClient, pCmdData); break; + case NetImgui::Internal::CmdHeader::eCommands::Clipboard: Communications_Incoming_CmdClipboard(pClient, pCmdData); break; + // Commands not received in main loop, by Server + case NetImgui::Internal::CmdHeader::eCommands::Version: + case NetImgui::Internal::CmdHeader::eCommands::Input: + case NetImgui::Internal::CmdHeader::eCommands::_Invalid: break; } } - + else + { + pClient->mbDisconnectPending = true; // Connection problem detected, close connection + } NetImgui::Internal::netImguiDeleteSafe(pCmdData); - } - pClient->mStatsDataRcvd += frameDataReceived; - return bOk; + else + { + // Prevent high CPU usage when waiting for new data + //std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(250)); + } } //================================================================================================= // Send the updates to RemoteClient // Ends with a Ping Command (signal a end of commands) //================================================================================================= -bool Communications_Outgoing(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) +void Communications_Outgoing(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) { - bool bSuccess(true); NetImgui::Internal::CmdInput* pInputCmd = pClient->TakePendingInput(); NetImgui::Internal::CmdClipboard* pClipboardCmd = pClient->TakePendingClipboard(); - uint64_t frameDataSent(0); + if( pInputCmd ) { - bSuccess &= NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(pInputCmd), pInputCmd->mHeader.mSize); - frameDataSent += pInputCmd->mHeader.mSize; - NetImgui::Internal::netImguiDeleteSafe(pInputCmd); + if( NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(pInputCmd), pInputCmd->mHeader.mSize) ) + { + pClient->mStatsDataSent += pInputCmd->mHeader.mSize; + NetImgui::Internal::netImguiDeleteSafe(pInputCmd); + } } - + if( pClipboardCmd ) { pClipboardCmd->ToOffsets(); - bSuccess &= NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(pClipboardCmd), pClipboardCmd->mHeader.mSize); - frameDataSent += pClipboardCmd->mHeader.mSize; - NetImgui::Internal::netImguiDeleteSafe(pClipboardCmd); + if(NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(pClipboardCmd), pClipboardCmd->mHeader.mSize) ) + { + pClient->mStatsDataSent += pClipboardCmd->mHeader.mSize; + NetImgui::Internal::netImguiDeleteSafe(pClipboardCmd); + } } - if( pClient->mbDisconnectPending ) + if( gbShutdown || pClient->mbDisconnectPending ) { NetImgui::Internal::CmdDisconnect cmdDisconnect; - bSuccess &= NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdDisconnect), cmdDisconnect.mHeader.mSize); - frameDataSent += cmdDisconnect.mHeader.mSize; - } - - // Always finish with a ping - { - NetImgui::Internal::CmdPing cmdPing; - bSuccess &= NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdPing), cmdPing.mHeader.mSize); - frameDataSent += cmdPing.mHeader.mSize; + NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdDisconnect), cmdDisconnect.mHeader.mSize); + pClient->mbIsConnected = false; } - pClient->mStatsDataSent += frameDataSent; - return bSuccess; } //================================================================================================= @@ -194,11 +191,6 @@ void Communications_UpdateClientStats(RemoteClient::Client& Client) Client.mStatsDataRcvdPrev = Client.mStatsDataRcvd; Client.mStatsDataSentPrev = Client.mStatsDataSent; } - - // Reset FPS to zero, when detected as not visible - if( !Client.mbIsVisible ){ - Client.mStatsFPS = 0.f; - } } //================================================================================================= @@ -206,21 +198,18 @@ void Communications_UpdateClientStats(RemoteClient::Client& Client) //================================================================================================= void Communications_ClientExchangeLoop(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pClient) { - bool bConnected(true); gActiveClientThreadCount++; NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::Connected); - while (bConnected && !gbShutdown && pClient->mbIsConnected && !pClient->mbDisconnectPending ) + while ( pClient->mbIsConnected ) { - bConnected = Communications_Outgoing(pClientSocket, pClient) && - Communications_Incoming(pClientSocket, pClient); - + Communications_Outgoing(pClientSocket, pClient); + Communications_Incoming(pClientSocket, pClient); Communications_UpdateClientStats(*pClient); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::Disconnected); NetImgui::Internal::Network::Disconnect(pClientSocket); - pClient->Release(); gActiveClientThreadCount--; } @@ -238,43 +227,58 @@ bool Communications_InitializeClient(NetImgui::Internal::Network::SocketInfo* pC 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) ) + //--------------------------------------------------------------------- + // Handshake confirming connection validity + //--------------------------------------------------------------------- + if( NetImgui::Internal::Network::DataSend(pClientSocket, reinterpret_cast(&cmdVersionSend), cmdVersionSend.mHeader.mSize) ) { - if( cmdVersionRcv.mHeader.mType != NetImgui::Internal::CmdHeader::eCommands::Version || - cmdVersionRcv.mVersion != NetImgui::Internal::CmdVersion::eVersion::_current ) + while( !NetImgui::Internal::Network::DataReceivePending(pClientSocket) ) { - NetImguiServer::Config::Client::SetProperty_Status(pClient->mClientConfigID, NetImguiServer::Config::Client::eStatus::ErrorVer); - return false; + std::this_thread::yield(); // Idle until we receive the remote data } - else if(cmdVersionRcv.mFlags & static_cast(NetImgui::Internal::CmdVersion::eFlags::IsConnected) ) + if( NetImgui::Internal::Network::DataReceive(pClientSocket, reinterpret_cast(&cmdVersionRcv), sizeof(cmdVersionRcv)) ) { - 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; - } + //--------------------------------------------------------------------- + // Connection accepted, initialize client + //--------------------------------------------------------------------- + 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; - NetImgui::Internal::StringCopy(pClient->mInfoName, cmdVersionRcv.mClientName); - NetImgui::Internal::StringCopy(pClient->mInfoImguiVerName, cmdVersionRcv.mImguiVerName); - NetImgui::Internal::StringCopy(pClient->mInfoNetImguiVerName, cmdVersionRcv.mNetImguiVerName); + pClient->Initialize(); + pClient->mInfoImguiVerID = cmdVersionRcv.mImguiVerID; + pClient->mInfoNetImguiVerID = cmdVersionRcv.mNetImguiVerID; + NetImgui::Internal::StringCopy(pClient->mInfoName, cmdVersionRcv.mClientName); + NetImgui::Internal::StringCopy(pClient->mInfoImguiVerName, cmdVersionRcv.mImguiVerName); + NetImgui::Internal::StringCopy(pClient->mInfoNetImguiVerName, cmdVersionRcv.mNetImguiVerName); - NetImguiServer::Config::Client clientConfig; - if( NetImguiServer::Config::Client::GetConfigByID(pClient->mClientConfigID, clientConfig) ){ - NetImgui::Internal::StringFormat(pClient->mWindowID, "%s (%s)##%i", pClient->mInfoName, clientConfig.mClientName, static_cast(pClient->mClientIndex)); // Using ClientIndex as a window unique ID - } - else{ - NetImgui::Internal::StringFormat(pClient->mWindowID, "%s##%i", pClient->mInfoName, static_cast(pClient->mClientIndex)); // Using ClientIndex as a window unique ID + NetImguiServer::Config::Client clientConfig; + if( NetImguiServer::Config::Client::GetConfigByID(pClient->mClientConfigID, clientConfig) ){ + NetImgui::Internal::StringFormat(pClient->mWindowID, "%s (%s)##%i", pClient->mInfoName, clientConfig.mClientName, static_cast(pClient->mClientIndex)); // Using ClientIndex as a window unique ID + } + else{ + NetImgui::Internal::StringFormat(pClient->mWindowID, "%s##%i", pClient->mInfoName, static_cast(pClient->mClientIndex)); // Using ClientIndex as a window unique ID + } + return true; } - - return true; } return false; } +//================================================================================================= +// New connection Init request. +// Start new communication thread if handshake sucessfull +//================================================================================================= void NetworkConnectionNew(NetImgui::Internal::Network::SocketInfo* pClientSocket, RemoteClient::Client* pNewClient, bool ConnectForce) { const char* zErrorMsg(nullptr); diff --git a/Code/ServerApp/Source/NetImguiServer_RemoteClient.cpp b/Code/ServerApp/Source/NetImguiServer_RemoteClient.cpp index 759012f..f656f2b 100644 --- a/Code/ServerApp/Source/NetImguiServer_RemoteClient.cpp +++ b/Code/ServerApp/Source/NetImguiServer_RemoteClient.cpp @@ -62,13 +62,12 @@ void Client::ReceiveDrawFrame(NetImgui::Internal::CmdDrawFrame* pFrameData) mpFrameDrawPrev = pFrameData; NetImguiImDrawData* pNewDrawData = ConvertToImguiDrawData(pFrameData); mPendingImguiDrawDataIn.Assign(pNewDrawData); - + // Update framerate - constexpr float kHysteresis = 0.05f; // Between 0 to 1.0 + constexpr float kHysteresis = 0.025f; // Between 0 to 1.0 auto elapsedTime = std::chrono::steady_clock::now() - mLastDrawFrame; - float tmMicroS = static_cast(std::chrono::duration_cast(elapsedTime).count()); - float newFPS = 1000000.f / tmMicroS; - mStatsFPS = mStatsFPS * (1.f-kHysteresis) + newFPS*kHysteresis; + float elapsedMs = static_cast(std::chrono::duration_cast(elapsedTime).count()) / 1000.f; + mStatsDrawElapsedMs = mStatsDrawElapsedMs * (1.f-kHysteresis) + elapsedMs*kHysteresis; mLastDrawFrame = std::chrono::steady_clock::now(); } } @@ -127,20 +126,21 @@ void Client::ProcessPendingTextures() void Client::Initialize() { - mConnectedTime = std::chrono::steady_clock::now(); - mLastUpdateTime = std::chrono::steady_clock::now() - std::chrono::hours(1); - mLastDrawFrame = std::chrono::steady_clock::now(); - mStatsIndex = 0; - mStatsRcvdBps = 0; - mStatsSentBps = 0; - mStatsFPS = 0.f; - mStatsDataRcvd = 0; - mStatsDataSent = 0; - mStatsDataRcvdPrev = 0; - mStatsDataSentPrev = 0; - mbIsReleased = false; - mStatsTime = std::chrono::steady_clock::now(); - mBGSettings = NetImgui::Internal::CmdBackground(); // Assign background default value, until we receive first update from client + mConnectedTime = std::chrono::steady_clock::now(); + mLastUpdateTime = std::chrono::steady_clock::now() - std::chrono::hours(1); + mLastDrawFrame = std::chrono::steady_clock::now(); + mLastIncomingComTime = std::chrono::steady_clock::now(); + mStatsIndex = 0; + mStatsRcvdBps = 0; + mStatsSentBps = 0; + mStatsDrawElapsedMs = 0.f; + mStatsDataRcvd = 0; + mStatsDataSent = 0; + mStatsDataRcvdPrev = 0; + mStatsDataSentPrev = 0; + mbIsReleased = false; + mStatsTime = std::chrono::steady_clock::now(); + mBGSettings = NetImgui::Internal::CmdBackground(); // Assign background default value, until we receive first update from client NetImgui::Internal::netImguiDeleteSafe(mpImguiDrawData); NetImgui::Internal::netImguiDeleteSafe(mpFrameDrawPrev); } @@ -371,34 +371,35 @@ void Client::CaptureImguiInput() { // Capture input from Dear ImGui (when this Client is in focus) const ImGuiIO& io = ImGui::GetIO(); - if( ImGui::IsWindowFocused() ) { - const size_t initialSize = mPendingInputChars.size(); - const size_t addedChar = io.InputQueueCharacters.size(); - if( addedChar ){ - mPendingInputChars.resize(initialSize+addedChar); - memcpy(&mPendingInputChars[initialSize], io.InputQueueCharacters.Data, addedChar*sizeof(ImWchar)); - } + if( mbIsVisible ){ + if( ImGui::IsWindowFocused() ){ + const size_t initialSize = mPendingInputChars.size(); + const size_t addedChar = io.InputQueueCharacters.size(); + if( addedChar ){ + mPendingInputChars.resize(initialSize+addedChar); + memcpy(&mPendingInputChars[initialSize], io.InputQueueCharacters.Data, addedChar*sizeof(ImWchar)); + } - mMouseWheelPos[0] += io.MouseWheel; - mMouseWheelPos[1] += io.MouseWheelH; - } + mMouseWheelPos[0] += io.MouseWheel; + mMouseWheelPos[1] += io.MouseWheelH; + } - // Update persistent mouse status - if( ImGui::IsMousePosValid(&io.MousePos)){ - mMousePos[0] = io.MousePos.x - ImGui::GetCursorScreenPos().x; - mMousePos[1] = io.MousePos.y - ImGui::GetCursorScreenPos().y; + // Update persistent mouse status + if( ImGui::IsMousePosValid(&io.MousePos)){ + mMousePos[0] = io.MousePos.x - ImGui::GetCursorScreenPos().x; + mMousePos[1] = io.MousePos.y - ImGui::GetCursorScreenPos().y; + } } // This method is tied to the Server VSync setting, which might not match our client desired refresh setting // When client refresh drops too much, take into consideration the lenght of the Server frame, to evaluate if we should update or not bool wasActive = mbIsActive; mbIsActive = ImGui::IsWindowFocused(); - float refreshFPS = mbIsActive ? NetImguiServer::Config::Server::sRefreshFPSActive : NetImguiServer::Config::Server::sRefreshFPSInactive; + float clientFPS = !mbIsVisible ? 0.f + : !mbIsActive ? NetImguiServer::Config::Server::sRefreshFPSInactive + : NetImguiServer::Config::Server::sRefreshFPSActive; float elapsedMs = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - mLastUpdateTime).count()) / 1000.f; - elapsedMs += mStatsFPS < refreshFPS ? 1000.f / (NetImguiServer::UI::GetDisplayFPS()/2.f) : 0.f; - bool bRefresh = wasActive != mbIsActive || // Important to sent input to client losing focus, making sure it knows to release the keypress - (elapsedMs > 60000.f) || // Keep 1 refresh per minute minimum - (refreshFPS >= 0.01f && elapsedMs > 1000.f/refreshFPS); + bool bRefresh = (wasActive != mbIsActive) || elapsedMs > 1000.f/60.f; if( !bRefresh ){ return; @@ -420,9 +421,10 @@ void Client::CaptureImguiInput() pNewInput->mCompressionUse = NetImguiServer::Config::Server::sCompressionEnable; pNewInput->mCompressionSkip = mbCompressionSkipOncePending; pNewInput->mFontDPIScaling = config.mDPIScaleEnabled ? NetImguiServer::UI::GetFontDPIScale() : 1.f; + pNewInput->mDesiredFps = clientFPS; mbCompressionSkipOncePending = false; - if( ImGui::IsWindowFocused() ) + if( (mbIsVisible && mbIsActive) && ImGui::IsWindowFocused() ) { // Mouse Buttons Inputs // If Dear ImGui Update this enum, must also adjust our enum copy diff --git a/Code/ServerApp/Source/NetImguiServer_RemoteClient.h b/Code/ServerApp/Source/NetImguiServer_RemoteClient.h index 487f344..b905362 100644 --- a/Code/ServerApp/Source/NetImguiServer_RemoteClient.h +++ b/Code/ServerApp/Source/NetImguiServer_RemoteClient.h @@ -91,7 +91,8 @@ struct Client std::atomic_bool mbCompressionSkipOncePending; //!< When we detect invalid previous DrawFrame command, cancel compression for 1 frame, to get good data std::chrono::steady_clock::time_point mConnectedTime; //!< When the connection was established with this remote client std::chrono::steady_clock::time_point mLastUpdateTime; //!< When the client last send a content refresh request - std::chrono::steady_clock::time_point mLastDrawFrame; //!< When we last receive a new drawframe commant + std::chrono::steady_clock::time_point mLastDrawFrame; //!< When we last receive a new drawframe commant + std::chrono::steady_clock::time_point mLastIncomingComTime; //!< When we last received a valid command from client (to detect timeout) uint32_t mClientConfigID; //!< ID of ClientConfig that connected (if connection came from our list of ClientConfigs) uint32_t mClientIndex = 0; //!< Entry idx into table of connected clients uint64_t mStatsDataRcvd; //!< Current amount of Bytes received since connected @@ -101,14 +102,14 @@ struct Client std::chrono::steady_clock::time_point mStatsTime; //!< Time when info was collected (with history of last x values) uint32_t mStatsRcvdBps; //!< Average Bytes received per second uint32_t mStatsSentBps; //!< Average Bytes sent per second - float mStatsFPS; //!< Average refresh rate of content - uint32_t mStatsIndex; - float mMousePos[2] = {0,0}; - float mMouseWheelPos[2] = {0,0}; - ImGuiMouseCursor mMouseCursor = ImGuiMouseCursor_None; // Last mosue cursor remote client requested - ImGuiContext* mpBGContext = nullptr; // Special Imgui Context used to render the background (only updated when needed) - bool mBGNeedUpdate = true; // Let engine know that we should regenerate the background draw commands - NetImgui::Internal::CmdBackground mBGSettings; // Settings for client background drawing settings + float mStatsDrawElapsedMs = 0.f; //!< Average milliseconds between 2 draw requests + uint32_t mStatsIndex = 0; + float mMousePos[2] = {0,0}; + float mMouseWheelPos[2] = {0,0}; + ImGuiMouseCursor mMouseCursor = ImGuiMouseCursor_None; // Last mosue cursor remote client requested + ImGuiContext* mpBGContext = nullptr; // Special Imgui Context used to render the background (only updated when needed) + bool mBGNeedUpdate = true; // Let engine know that we should regenerate the background draw commands + NetImgui::Internal::CmdBackground mBGSettings; // Settings for client background drawing settings static bool Startup(uint32_t clientCountMax); static void Shutdown(); diff --git a/Code/ServerApp/Source/NetImguiServer_UI.cpp b/Code/ServerApp/Source/NetImguiServer_UI.cpp index 93ee5ee..59f3861 100644 --- a/Code/ServerApp/Source/NetImguiServer_UI.cpp +++ b/Code/ServerApp/Source/NetImguiServer_UI.cpp @@ -101,7 +101,7 @@ void ClientInfoTooltip(const RemoteClient::Client& Client) ImGui::TextUnformatted("Port"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": %i", Client.mConnectPort); ImGui::TextUnformatted("ImGui"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": %s", Client.mInfoImguiVerName); ImGui::TextUnformatted("Time"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": %03ih%02i:%02i", tmHour,tmMin,tmSec ); - ImGui::TextUnformatted("Fps"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": %04.1f", Client.mStatsFPS ); + ImGui::TextUnformatted("Fps"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": %04.1f", Client.mbIsVisible ? 1000.f/Client.mStatsDrawElapsedMs : 0.f); ImGui::TextUnformatted("Data"); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": (Rx) %7i KB/s \t(Tx) %7i KB/s", Client.mStatsRcvdBps/1024, Client.mStatsSentBps/1024); ImGui::NewLine(); ImGui::SameLine(width); ImGui::TextColored(kColorContent, ": (Rx) %7i %s \t(Tx) %7i %s", static_cast(rxData), kDataSizeUnits[rxUnitIdx], static_cast(txData), kDataSizeUnits[txUnitIdx]); ImGui::EndTooltip(); @@ -540,14 +540,15 @@ void DrawImguiContent_Clients() ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); ClientInfoTooltip(client); ImGui::PopStyleVar(1); + + // Capture input to forward to remote client, and update drawing area size + ImVec2 areaSize = ImGui::GetContentRegionAvail(); + client.mAreaSizeX = static_cast(std::max(8.f,areaSize.x)); // Prevents issue with render target of size 0 + client.mAreaSizeY = static_cast(std::max(8.f,areaSize.y)); + client.CaptureImguiInput(); + if( client.mbIsVisible ) { - // Capture input to forward to remote client, and update drawing area size - ImVec2 areaSize = ImGui::GetContentRegionAvail(); - client.mAreaSizeX = static_cast(std::max(8.f,areaSize.x)); // Prevents issue with render target of size 0 - client.mAreaSizeY = static_cast(std::max(8.f,areaSize.y)); - client.CaptureImguiInput(); - // Display remote client drawing results if (client.mpHAL_AreaTexture && areaSize.x > 0 && areaSize.y > 0) { @@ -889,9 +890,9 @@ void DrawImguiContent_MainMenu_Stats() if (ImGui::IsItemHovered()) { uint64_t txData(NetImguiServer::Network::GetStatsDataSent()); - uint64_t rxData(NetImguiServer::Network::GetStatsDataRcvd()); + uint64_t rxData(NetImguiServer::Network::GetStatsDataRcvd()); uint8_t txUnitIdx = ConvertDataAmount(txData); - uint8_t rxUnitIdx = ConvertDataAmount(rxData); + uint8_t rxUnitIdx = ConvertDataAmount(rxData); ImGui::BeginTooltip(); { float width = ImGui::CalcTextSize("Data Received ").x;