diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index d0db2e14f..9d9947cca 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "Code/QRDUtils.h" #include "Styles/StyleData.h" @@ -194,34 +195,78 @@ void PersistantConfig::applyValues(const QVariantMap &values) RENAMED_SETTING(QVariantList, SPIRVDisassemblers, ShaderProcessors); } -int PersistantConfig::RemoteHostCount() +static QMutex RemoteHostLock; + +rdcarray PersistantConfig::GetRemoteHosts() { - return RemoteHosts.count(); + QMutexLocker autolock(&RemoteHostLock); + return RemoteHostList; } -RemoteHost *PersistantConfig::GetRemoteHost(int index) +RemoteHost PersistantConfig::GetRemoteHost(const rdcstr &hostname) { - if(index < 0 || index >= RemoteHostCount()) - return NULL; - return RemoteHosts[index]; + RemoteHost ret; + + { + QMutexLocker autolock(&RemoteHostLock); + for(size_t i = 0; i < RemoteHostList.size(); i++) + { + if(RemoteHostList[i].Hostname() == hostname) + { + ret = RemoteHostList[i]; + break; + } + } + } + + return ret; } void PersistantConfig::AddRemoteHost(RemoteHost host) { - RemoteHosts.push_back(new RemoteHost(host)); + if(!host.IsValid()) + return; + + QMutexLocker autolock(&RemoteHostLock); + + // don't add duplicates + for(size_t i = 0; i < RemoteHostList.size(); i++) + { + if(RemoteHostList[i] == host) + { + RemoteHostList[i] = host; + return; + } + } + + RemoteHostList.push_back(host); +} + +void PersistantConfig::RemoveRemoteHost(RemoteHost host) +{ + if(!host.IsValid()) + return; + + QMutexLocker autolock(&RemoteHostLock); + + for(size_t i = 0; i < RemoteHostList.size(); i++) + { + if(RemoteHostList[i] == host) + { + RemoteHostList.takeAt(i); + break; + } + } } void PersistantConfig::AddAndroidHosts() { - QMap oldHosts; - for(int i = RemoteHosts.count() - 1; i >= 0; i--) - { - if(RemoteHosts[i]->IsADB()) - { - RemoteHost *host = RemoteHosts.takeAt(i); - oldHosts[host->hostname] = host; - } - } + QMutexLocker autolock(&RemoteHostLock); + + QMap oldHosts; + for(int i = RemoteHostList.count() - 1; i >= 0; i--) + if(RemoteHostList[i].IsADB()) + oldHosts[RemoteHostList[i].Hostname()] = RemoteHostList.takeAt(i); QString androidSDKPath = (!Android_SDKPath.isEmpty() && QFile::exists(Android_SDKPath)) ? QString(Android_SDKPath) @@ -242,28 +287,25 @@ void PersistantConfig::AddAndroidHosts() for(const QString &hostName : QString(androidHosts).split(QLatin1Char(','), QString::SkipEmptyParts)) { - RemoteHost *host = NULL; + RemoteHost host((rdcstr)hostName); if(oldHosts.contains(hostName)) host = oldHosts.take(hostName); - else - host = new RemoteHost(); - host->hostname = hostName; rdcstr friendly; RENDERDOC_GetAndroidFriendlyName(hostName.toUtf8().data(), friendly); - host->friendlyName = friendly; + host.SetFriendlyName(friendly); // Just a command to display in the GUI and allow Launch() to be called. - host->runCommand = lit("Automatically handled"); - RemoteHosts.push_back(host); + host.SetRunCommand("Automatically handled"); + RemoteHostList.push_back(host); } // delete any leftovers - QMapIterator i(oldHosts); + QMutableMapIterator i(oldHosts); while(i.hasNext()) { i.next(); - delete i.value(); + i.value().SetShutdown(); } } @@ -288,8 +330,6 @@ bool PersistantConfig::SetStyle() PersistantConfig::~PersistantConfig() { - for(RemoteHost *h : RemoteHosts) - delete h; } bool PersistantConfig::Load(const rdcstr &filename) @@ -309,24 +349,32 @@ bool PersistantConfig::Load(const rdcstr &filename) RDDialog::DefaultBrowsePath = LastFileBrowsePath; // localhost should always be available as a remote host - bool foundLocalhost = false; - - for(RemoteHost host : RemoteHostList) { - if(host.hostname.isEmpty()) - continue; + QMutexLocker autolock(&RemoteHostLock); - RemoteHosts.push_back(new RemoteHost(host)); + bool foundLocalhost = false; - if(host.IsLocalhost()) - foundLocalhost = true; - } + rdcarray hosts; + hosts.swap(RemoteHostList); - if(!foundLocalhost) - { - RemoteHost *host = new RemoteHost(); - host->hostname = "localhost"; - RemoteHosts.insert(0, host); + for(RemoteHost host : hosts) + { + // skip invalid hosts + if(!host.IsValid()) + continue; + + RemoteHostList.push_back(host); + + if(host.IsLocalhost()) + foundLocalhost = true; + } + + if(!foundLocalhost) + { + RemoteHost host; + host.m_hostname = "localhost"; + RemoteHostList.insert(0, host); + } } bool tools[arraydim()] = {}; @@ -427,11 +475,6 @@ bool PersistantConfig::Save() if(m_Filename.isEmpty()) return true; - // update serialize list - RemoteHostList.clear(); - for(RemoteHost *host : RemoteHosts) - RemoteHostList.push_back(*host); - RENDERDOC_SetConfigSetting("Disassembly_FriendlyNaming", ShaderViewer_FriendlyNaming ? "1" : "0"); RENDERDOC_SetConfigSetting("ExternalTool_RGPIntegration", ExternalTool_RGPIntegration ? "1" : "0"); diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index b32a94926..7f8a2e693 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -28,6 +28,8 @@ #include "QRDInterface.h" #include "RemoteHost.h" +class QMutex; + DOCUMENT(R"(Identifies a particular known tool used for shader processing. .. data:: Unknown @@ -766,38 +768,29 @@ For more information about some of these settings that are user-facing see class PersistantConfig { public: -// don't allow SWIG direct access to the RemoteHosts since they're an array of references and our -// bindings can't handle that properly -#if !defined(SWIG) - // Runtime list of dynamically allocated hosts. - // Saved to/from private RemoteHostList in CONFIG_SETTINGS() - // This is documented above in the docstring, similar to the values in CONFIG_SETTINGS() - // This must only be accessed on the UI thread to prevent races. For access on other threads (e.g. - // a background/asynchronous update), take a copy on the UI thread, update it in the background, - // then apply the updates. - DOCUMENT(""); - rdcarray RemoteHosts; -#endif + DOCUMENT(R"(Returns a list of all remote hosts. - DOCUMENT(R"(Returns the number of remote hosts currently registered. - -:return: The number of remote hosts. -:rtype: ``int`` +:return: The remote host list +:rtype: ``list`` of ``RemoteHost`` )"); - int RemoteHostCount(); - DOCUMENT(R"(Returns a given remote host at an index. + rdcarray GetRemoteHosts(); + DOCUMENT(R"(Look up a remote host by hostname. -:param int index: The index of the remote host to retrieve -:return: The remote host specified, or ``None`` if an invalid index was passed +:return: The remote host for the given hostname, or an invalid ``RemoteHost`` if no such exists. :rtype: ``RemoteHost`` )"); - RemoteHost *GetRemoteHost(int index); + RemoteHost GetRemoteHost(const rdcstr &hostname); DOCUMENT(R"(Adds a new remote host. :param RemoteHost host: The remote host to add. R)"); void AddRemoteHost(RemoteHost host); + DOCUMENT(R"(Removes an existing remote host. + +:param RemoteHost host: The remote host to remove. +R)"); + void RemoveRemoteHost(RemoteHost host); DOCUMENT("If configured, queries ``adb`` to add android hosts to :data:`RemoteHosts`."); void AddAndroidHosts(); diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 453f80cde..3e601e761 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -735,7 +735,7 @@ struct IReplayManager :return: Whether or not the connection was successful. :rtype: ~renderdoc.ReplayStatus )"); - virtual ReplayStatus ConnectToRemoteServer(RemoteHost *host) = 0; + virtual ReplayStatus ConnectToRemoteServer(RemoteHost host) = 0; DOCUMENT("Disconnect from the server the manager is currently connected to."); virtual void DisconnectFromRemoteServer() = 0; @@ -751,10 +751,10 @@ struct IReplayManager DOCUMENT(R"(Retrieves the host that the manager is currently connected to. -:return: The host connected to, or ``None`` if no connection is active. +:return: The host connected to, or an invalid RemoteHost if no connection is active. :rtype: RemoteHost )"); - virtual RemoteHost *CurrentRemote() = 0; + virtual RemoteHost CurrentRemote() = 0; DOCUMENT(R"(Retrieves the capture file handle for the currently open file. diff --git a/qrenderdoc/Code/Interface/RemoteHost.cpp b/qrenderdoc/Code/Interface/RemoteHost.cpp index c060f58ba..1df93398d 100644 --- a/qrenderdoc/Code/Interface/RemoteHost.cpp +++ b/qrenderdoc/Code/Interface/RemoteHost.cpp @@ -22,80 +22,158 @@ * THE SOFTWARE. ******************************************************************************/ +#include +#include +#include #include #include #include "Code/QRDUtils.h" #include "QRDInterface.h" -RemoteHost::RemoteHost() +struct RemoteHostData { - serverRunning = connected = busy = versionMismatch = false; -} + QAtomicInt refcount; + QMutex mutex; + + void ref() { refcount.ref(); } + void deref() + { + if(!refcount.deref()) + delete this; + } + + RemoteHostData() : refcount(1) {} + rdcstr m_friendlyName, m_runCommand, m_lastCapturePath; + bool m_serverRunning = false, m_connected = false, m_busy = false, m_versionMismatch = false; +}; RemoteHost::RemoteHost(const QVariant &var) { + m_data = new RemoteHostData(); + QVariantMap map = var.toMap(); if(map.contains(lit("hostname"))) - hostname = map[lit("hostname")].toString(); + m_hostname = map[lit("hostname")].toString(); if(map.contains(lit("friendlyName"))) - friendlyName = map[lit("friendlyName")].toString(); + m_data->m_friendlyName = map[lit("friendlyName")].toString(); if(map.contains(lit("runCommand"))) - runCommand = map[lit("runCommand")].toString(); + m_data->m_runCommand = map[lit("runCommand")].toString(); if(map.contains(lit("lastCapturePath"))) - lastCapturePath = map[lit("lastCapturePath")].toString(); + m_data->m_lastCapturePath = map[lit("lastCapturePath")].toString(); +} - serverRunning = connected = busy = versionMismatch = false; +RemoteHost::RemoteHost() +{ + m_data = new RemoteHostData(); +} + +RemoteHost::RemoteHost(const rdcstr &host) +{ + // create a new host + m_hostname = host; + m_data = new RemoteHostData(); +} + +RemoteHost::RemoteHost(const RemoteHost &o) +{ + *this = o; +} + +RemoteHost &RemoteHost::operator=(const RemoteHost &o) +{ + m_hostname = o.m_hostname; + + // deref old data + if(m_data) + m_data->deref(); + // point to new data + m_data = o.m_data; + // get a ref on it + m_data->ref(); + return *this; +} + +RemoteHost::~RemoteHost() +{ + m_data->deref(); } RemoteHost::operator QVariant() const { QVariantMap map; - map[lit("hostname")] = hostname; - map[lit("friendlyName")] = friendlyName; - map[lit("runCommand")] = runCommand; - map[lit("lastCapturePath")] = lastCapturePath; + m_data->mutex.lock(); + map[lit("hostname")] = m_hostname; + map[lit("friendlyName")] = m_data->m_friendlyName; + map[lit("runCommand")] = m_data->m_runCommand; + map[lit("lastCapturePath")] = m_data->m_lastCapturePath; + m_data->mutex.unlock(); return map; } void RemoteHost::CheckStatus() { // special case - this is the local context - if(hostname == "localhost") + if(m_hostname == "localhost") { - serverRunning = false; - versionMismatch = busy = false; + QMutexLocker autolock(&m_data->mutex); + m_data->m_serverRunning = m_data->m_versionMismatch = m_data->m_busy = false; return; } + // to avoid doing complex work while holding the remote host lock, we check the status here then + // call into the internal function that will propagate that data to the proper storage if needed. IRemoteServer *rend = NULL; - ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(hostname.c_str(), 0, &rend); - - if(status == ReplayStatus::Succeeded) - { - serverRunning = true; - versionMismatch = busy = false; - } - else if(status == ReplayStatus::NetworkRemoteBusy) - { - serverRunning = true; - busy = true; - versionMismatch = false; - } - else if(status == ReplayStatus::NetworkVersionMismatch) - { - serverRunning = true; - busy = true; - versionMismatch = true; - } - else - { - serverRunning = false; - versionMismatch = busy = false; - } + ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(m_hostname.c_str(), 0, &rend); if(rend) rend->ShutdownConnection(); + UpdateStatus(status); +} + +void RemoteHost::SetConnected(bool connected) +{ + QMutexLocker autolock(&m_data->mutex); + m_data->m_connected = connected; +} + +void RemoteHost::SetShutdown() +{ + QMutexLocker autolock(&m_data->mutex); + m_data->m_connected = false; + m_data->m_serverRunning = false; + m_data->m_busy = false; +} + +void RemoteHost::UpdateStatus(ReplayStatus status) +{ + { + QMutexLocker autolock(&m_data->mutex); + + if(status == ReplayStatus::Succeeded) + { + m_data->m_serverRunning = true; + m_data->m_versionMismatch = m_data->m_busy = false; + } + else if(status == ReplayStatus::NetworkRemoteBusy) + { + m_data->m_serverRunning = true; + m_data->m_busy = true; + m_data->m_versionMismatch = false; + } + else if(status == ReplayStatus::NetworkVersionMismatch) + { + m_data->m_serverRunning = true; + m_data->m_busy = true; + m_data->m_versionMismatch = true; + } + else + { + m_data->m_serverRunning = false; + m_data->m_versionMismatch = m_data->m_busy = false; + } + } + // since we can only have one active client at once on a remote server, we need // to avoid DDOS'ing by doing multiple CheckStatus() one after the other so fast // that the active client can't be properly shut down. Sleeping here for a short @@ -113,15 +191,82 @@ ReplayStatus RemoteHost::Launch() if(IsADB()) { - status = RENDERDOC_StartAndroidRemoteServer(hostname.c_str()); + status = RENDERDOC_StartAndroidRemoteServer(m_hostname.c_str()); QThread::msleep(WAIT_TIME); return status; } + rdcstr run; + + { + QMutexLocker autolock(&m_data->mutex); + run = RunCommand(); + } + RDProcess process; - process.start(runCommand); + process.start(run); process.waitForFinished(WAIT_TIME); process.detach(); return status; } + +bool RemoteHost::IsServerRunning() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_serverRunning; +} + +bool RemoteHost::IsConnected() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_connected; +} + +bool RemoteHost::IsBusy() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_busy; +} + +bool RemoteHost::IsVersionMismatch() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_versionMismatch; +} + +rdcstr RemoteHost::FriendlyName() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_friendlyName; +} + +void RemoteHost::SetFriendlyName(const rdcstr &name) +{ + QMutexLocker autolock(&m_data->mutex); + m_data->m_friendlyName = name; +} + +rdcstr RemoteHost::RunCommand() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_runCommand; +} + +void RemoteHost::SetRunCommand(const rdcstr &cmd) +{ + QMutexLocker autolock(&m_data->mutex); + m_data->m_runCommand = cmd; +} + +rdcstr RemoteHost::LastCapturePath() const +{ + QMutexLocker autolock(&m_data->mutex); + return m_data->m_lastCapturePath; +} + +void RemoteHost::SetLastCapturePath(const rdcstr &path) +{ + QMutexLocker autolock(&m_data->mutex); + m_data->m_lastCapturePath = path; +} diff --git a/qrenderdoc/Code/Interface/RemoteHost.h b/qrenderdoc/Code/Interface/RemoteHost.h index 5eb6df143..bddb6b87a 100644 --- a/qrenderdoc/Code/Interface/RemoteHost.h +++ b/qrenderdoc/Code/Interface/RemoteHost.h @@ -29,22 +29,33 @@ class RemoteHost; // do not include any headers here, they must all be in QRDInterface.h #include "QRDInterface.h" +class PersistantConfig; +class ReplayManager; + +struct RemoteHostData; + +// to enable easy copying around of these hosts as well as allowing graceful behaviour when hosts +// are unexpectedly removed (such as disconnecting an auto-populated device) these structs are +// copied around and they have a shared locked data pointer. All accessors then lock and look up the +// data there to fetch or modify DOCUMENT("A handle for interacting with a remote server on a given host."); class RemoteHost { public: + DOCUMENT(""); RemoteHost(); - + RemoteHost(const rdcstr &host); + RemoteHost(const RemoteHost &o); +#if !defined(SWIG) + RemoteHost &operator=(const RemoteHost &o); +#endif + ~RemoteHost(); VARIANT_CAST(RemoteHost); DOCUMENT(""); - bool operator==(const RemoteHost &o) const - { - return hostname == o.hostname && friendlyName == o.friendlyName && runCommand == o.runCommand && - serverRunning == o.serverRunning && connected == o.connected && busy == o.busy && - versionMismatch == o.versionMismatch; - } + bool operator==(const RemoteHost &o) const { return m_hostname == o.m_hostname; } bool operator!=(const RemoteHost &o) const { return !(*this == o); } + bool operator<(const RemoteHost &o) const { return m_hostname < o.m_hostname; } DOCUMENT( "Ping the host to check current status - if the server is running, connection status, etc."); void CheckStatus(); @@ -54,36 +65,72 @@ public: ReplayStatus Launch(); DOCUMENT("``True`` if a remote server is currently running on this host."); - bool serverRunning : 1; + bool IsServerRunning() const; DOCUMENT("``True`` if an active connection exists to this remote server."); - bool connected : 1; + bool IsConnected() const; DOCUMENT("``True`` if someone else is currently connected to this server."); - bool busy : 1; + bool IsBusy() const; DOCUMENT("``True`` if there is a code version mismatch with this server."); - bool versionMismatch : 1; + bool IsVersionMismatch() const; DOCUMENT("The hostname of this host."); - rdcstr hostname; + const rdcstr &Hostname() const { return m_hostname; } DOCUMENT("The friendly name for this host, if available (if empty, the Hostname is used)."); - rdcstr friendlyName; + rdcstr FriendlyName() const; DOCUMENT("The command to run locally to try to launch the server remotely."); - rdcstr runCommand; + rdcstr RunCommand() const; + DOCUMENT(R"(Sets the run command. See :meth:`RunCommand`. - DOCUMENT("The last folder browser to on this host, to provide a reasonable default path."); - rdcstr lastCapturePath; +:param str cmd: The new command to set. +)"); + void SetRunCommand(const rdcstr &cmd); + + DOCUMENT("The last folder browsed to on this host, to provide a reasonable default path."); + rdcstr LastCapturePath() const; + DOCUMENT(R"(Sets the last folder browsed to. See :meth:`LastCapturePath`. + +:param str path: The new path to set. +)"); + void SetLastCapturePath(const rdcstr &path); DOCUMENT(R"( -Returns the name to display for this host in the UI, either :data:`friendlyName` or :data:`hostname` +Returns the name to display for this host in the UI, either :meth:`FriendlyName` if it is valid, or +:meth:`Hostname` if not. )"); - const rdcstr &Name() const { return !friendlyName.isEmpty() ? friendlyName : hostname; } + rdcstr Name() const + { + rdcstr friendlyName = FriendlyName(); + return !friendlyName.isEmpty() ? friendlyName : m_hostname; + } DOCUMENT("Returns ``True`` if this host represents a connected ADB (Android) device."); bool IsADB() const { - return hostname.count() > 4 && hostname[0] == 'a' && hostname[1] == 'd' && hostname[2] == 'b' && - hostname[3] == ':'; + return m_hostname.count() > 4 && m_hostname[0] == 'a' && m_hostname[1] == 'd' && + m_hostname[2] == 'b' && m_hostname[3] == ':'; } DOCUMENT("Returns ``True`` if this host represents the special localhost device."); - bool IsLocalhost() const { return hostname == "localhost"; } + bool IsLocalhost() const { return m_hostname == "localhost"; } + DOCUMENT("Returns ``True`` if this host represents a valid remote host."); + bool IsValid() const { return m_data && !m_hostname.isEmpty(); } +private: + // this is immutable and is used as a key to look up data, it's always valid as RemoteHost objects + // are created with it + rdcstr m_hostname; + + // self-deleting shared and locked data store + RemoteHostData *m_data = NULL; + + // allow config to set our data + friend class PersistantConfig; + void SetFriendlyName(const rdcstr &name); + + // allow ReplayManager to call these functions to change the status. Otherwise they are read-only + // except by calling CheckStatus() + friend class ReplayManager; + void SetConnected(bool connected); + void SetShutdown(); + + void UpdateStatus(ReplayStatus status); }; DECLARE_REFLECTION_STRUCT(RemoteHost); \ No newline at end of file diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index 094260e1c..45a52d3e8 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -305,11 +305,11 @@ void ReplayManager::CloseThread() m_Thread = NULL; } -ReplayStatus ReplayManager::ConnectToRemoteServer(RemoteHost *host) +ReplayStatus ReplayManager::ConnectToRemoteServer(RemoteHost host) { - ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(host->hostname.c_str(), 0, &m_Remote); + ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), 0, &m_Remote); - if(host->IsADB()) + if(host.IsADB()) { ANALYTIC_SET(UIFeatures.AndroidRemoteReplay, true); } @@ -322,7 +322,7 @@ ReplayStatus ReplayManager::ConnectToRemoteServer(RemoteHost *host) if(status == ReplayStatus::Succeeded) { - m_RemoteHost->connected = true; + m_RemoteHost.SetConnected(true); return status; } @@ -331,8 +331,7 @@ ReplayStatus ReplayManager::ConnectToRemoteServer(RemoteHost *host) void ReplayManager::DisconnectFromRemoteServer() { - if(m_RemoteHost) - m_RemoteHost->connected = false; + m_RemoteHost.SetConnected(false); if(m_Remote) { @@ -340,12 +339,14 @@ void ReplayManager::DisconnectFromRemoteServer() m_Remote->ShutdownConnection(); } - m_RemoteHost = NULL; + m_RemoteHost = RemoteHost(); m_Remote = NULL; } void ReplayManager::ShutdownServer() { + m_RemoteHost.SetShutdown(); + if(m_Remote) { QMutexLocker autolock(&m_RemoteLock); @@ -365,7 +366,7 @@ void ReplayManager::PingRemote() if(!IsRunning() || m_Thread->isCurrentThread()) { if(!m_Remote->Ping()) - m_RemoteHost->serverRunning = false; + m_RemoteHost.SetShutdown(); } m_RemoteLock.unlock(); } diff --git a/qrenderdoc/Code/ReplayManager.h b/qrenderdoc/Code/ReplayManager.h index 0465904b9..c4fc2fec8 100644 --- a/qrenderdoc/Code/ReplayManager.h +++ b/qrenderdoc/Code/ReplayManager.h @@ -68,7 +68,7 @@ public: void CloseThread(); - ReplayStatus ConnectToRemoteServer(RemoteHost *host); + ReplayStatus ConnectToRemoteServer(RemoteHost host); void DisconnectFromRemoteServer(); void ShutdownServer(); void PingRemote(); @@ -86,7 +86,7 @@ public: // work whether local or remote. ICaptureFile *GetCaptureFile() { return m_CaptureFile; } void ReopenCaptureFile(const QString &path); - RemoteHost *CurrentRemote() { return m_RemoteHost; } + RemoteHost CurrentRemote() { return m_RemoteHost; } ExecuteResult ExecuteAndInject(const rdcstr &exe, const rdcstr &workingDir, const rdcstr &cmdLine, const rdcarray &env, const rdcstr &capturefile, CaptureOptions opts); @@ -128,7 +128,7 @@ private: void PushInvoke(InvokeHandle *cmd); QMutex m_RemoteLock; - RemoteHost *m_RemoteHost = NULL; + RemoteHost m_RemoteHost; IRemoteServer *m_Remote = NULL; volatile bool m_Running; diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i index 92a0cb376..878cd5b73 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i @@ -86,6 +86,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, rdcstrpair) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, BugReport) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ExtensionMetadata) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DialogButton) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, RemoteHost) TEMPLATE_ARRAY_INSTANTIATE_PTR(rdcarray, ICaptureViewer) // unignore the function from above diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp index a7fa9ea58..e29995611 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp @@ -125,20 +125,24 @@ void PersistantConfig::Close() { } -int PersistantConfig::RemoteHostCount() +rdcarray PersistantConfig::GetRemoteHosts() { - return 0; + return {}; } -RemoteHost *PersistantConfig::GetRemoteHost(int index) +RemoteHost PersistantConfig::GetRemoteHost(const rdcstr &) { - return NULL; + return RemoteHost(); } void PersistantConfig::AddRemoteHost(RemoteHost host) { } +void PersistantConfig::RemoveRemoteHost(RemoteHost host) +{ +} + void PersistantConfig::AddAndroidHosts() { } @@ -166,7 +170,23 @@ rdcstr PersistantConfig::GetConfigSetting(const rdcstr &name) RemoteHost::RemoteHost() { - serverRunning = connected = busy = versionMismatch = false; +} + +RemoteHost::RemoteHost(const rdcstr &host) +{ +} + +RemoteHost::RemoteHost(const RemoteHost &o) +{ +} + +RemoteHost &RemoteHost::operator=(const RemoteHost &o) +{ + return *this; +} + +RemoteHost::~RemoteHost() +{ } void RemoteHost::CheckStatus() @@ -177,3 +197,58 @@ ReplayStatus RemoteHost::Launch() { return ReplayStatus::Succeeded; } + +bool RemoteHost::IsServerRunning() const +{ + return false; +} + +bool RemoteHost::IsConnected() const +{ + return false; +} + +bool RemoteHost::IsBusy() const +{ + return false; +} + +bool RemoteHost::IsVersionMismatch() const +{ + return false; +} + +rdcstr RemoteHost::FriendlyName() const +{ + return rdcstr(); +} + +void RemoteHost::SetFriendlyName(const rdcstr &name) +{ +} + +rdcstr RemoteHost::RunCommand() const +{ + return rdcstr(); +} + +void RemoteHost::SetRunCommand(const rdcstr &cmd) +{ +} + +rdcstr RemoteHost::LastCapturePath() const +{ + return rdcstr(); +} + +void RemoteHost::SetLastCapturePath(const rdcstr &path) +{ +} + +void RemoteHost::SetConnected(bool connected) +{ +} + +void RemoteHost::SetShutdown() +{ +} \ No newline at end of file diff --git a/qrenderdoc/Code/qrenderdoc.cpp b/qrenderdoc/Code/qrenderdoc.cpp index 21188ad2e..e5d201478 100644 --- a/qrenderdoc/Code/qrenderdoc.cpp +++ b/qrenderdoc/Code/qrenderdoc.cpp @@ -312,10 +312,11 @@ int main(int argc, char *argv[]) int replayHostIndex = -1; if(parser.isSet(replayhost)) { - QString replayHost = parser.value(replayhost); - for(int i = 0; i < config.RemoteHosts.count(); i++) + rdcstr replayHost = parser.value(replayhost); + rdcarray hosts = config.GetRemoteHosts(); + for(int i = 0; i < hosts.count(); i++) { - if(QString(config.RemoteHosts[i]->hostname) == replayHost) + if(hosts[i].Hostname() == replayHost) { replayHostIndex = i; break; diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 525fb0f2b..def1faa46 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -300,7 +300,7 @@ void CaptureDialog::on_exePath_textChanged(const QString &text) { QString path = dir.absolutePath(); - if(!m_Ctx.Replay().CurrentRemote()) + if(!m_Ctx.Replay().CurrentRemote().IsValid()) path = QDir::toNativeSeparators(path); // match the path separators from the path @@ -503,7 +503,7 @@ void CaptureDialog::CheckAndroidSetup(QString &filename) LambdaThread *scan = new LambdaThread([this, filename]() { - rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname; + rdcstr host = m_Ctx.Replay().CurrentRemote().Hostname(); RENDERDOC_CheckAndroidPackage(host.c_str(), filename.toUtf8().data(), &m_AndroidFlags); const bool debuggable = bool(m_AndroidFlags & AndroidFlags::Debuggable); @@ -535,9 +535,9 @@ void CaptureDialog::androidWarn_mouseClick() { QString exe = ui->exePath->text(); - const RemoteHost *remote = m_Ctx.Replay().CurrentRemote(); + RemoteHost remote = m_Ctx.Replay().CurrentRemote(); - if(!remote) + if(!remote.IsValid()) { RDDialog::critical(this, tr("Android server disconnected"), tr("You've been disconnected from the android server.\n\n" @@ -545,7 +545,7 @@ void CaptureDialog::androidWarn_mouseClick() return; } - rdcstr host = remote->hostname; + rdcstr host = remote.Hostname(); QString caption = tr("Application is not debuggable"); @@ -650,9 +650,9 @@ void CaptureDialog::on_exePathBrowse_clicked() initDir = dir.absolutePath(); initExe = f.fileName(); } - else if(m_Ctx.Replay().CurrentRemote()) + else if(m_Ctx.Replay().CurrentRemote().IsValid()) { - initDir = m_Ctx.Replay().CurrentRemote()->lastCapturePath; + initDir = m_Ctx.Replay().CurrentRemote().LastCapturePath(); } else if(!m_Ctx.Config().LastCapturePath.isEmpty()) { @@ -663,7 +663,7 @@ void CaptureDialog::on_exePathBrowse_clicked() QString filename; - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { VirtualFileDialog vfd(m_Ctx, initDir, this); RDDialog::show(&vfd); @@ -678,7 +678,7 @@ void CaptureDialog::on_exePathBrowse_clicked() { SetExecutableFilename(filename); - if(m_Ctx.Replay().CurrentRemote() && m_Ctx.Replay().CurrentRemote()->IsADB()) + if(m_Ctx.Replay().CurrentRemote().IsADB()) { CheckAndroidSetup(filename); } @@ -691,9 +691,9 @@ void CaptureDialog::on_workDirBrowse_clicked() if(initDir.isEmpty()) { - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { - initDir = m_Ctx.Replay().CurrentRemote()->lastCapturePath; + initDir = m_Ctx.Replay().CurrentRemote().LastCapturePath(); } else if(!QDir(initDir).exists()) { @@ -709,7 +709,7 @@ void CaptureDialog::on_workDirBrowse_clicked() QString dir; - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { VirtualFileDialog vfd(m_Ctx, initDir, this); vfd.setDirBrowse(); @@ -1021,12 +1021,12 @@ void CaptureDialog::SetExecutableFilename(const rdcstr &filename) { QString fn = filename; - if(!m_Ctx.Replay().CurrentRemote()) + if(!m_Ctx.Replay().CurrentRemote().IsValid()) fn = QDir::toNativeSeparators(QFileInfo(fn).absoluteFilePath()); ui->exePath->setText(fn); - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { // remove the filename itself before setting the last capture path. Try /, then \ as path // separator. If no separators are found, there is no path to set. @@ -1045,7 +1045,7 @@ void CaptureDialog::SetExecutableFilename(const rdcstr &filename) fn = QString(); } - m_Ctx.Replay().CurrentRemote()->lastCapturePath = fn; + m_Ctx.Replay().CurrentRemote().SetLastCapturePath(fn); } else { @@ -1122,9 +1122,7 @@ void CaptureDialog::UpdateGlobalHook() void CaptureDialog::UpdateRemoteHost() { - const RemoteHost *host = m_Ctx.Replay().CurrentRemote(); - - if(host && host->IsADB()) + if(m_Ctx.Replay().CurrentRemote().IsADB()) ui->cmdLineLabel->setText(tr("Intent Arguments")); else ui->cmdLineLabel->setText(tr("Command-line Arguments")); @@ -1188,7 +1186,7 @@ void CaptureDialog::TriggerCapture() } // for non-remote captures, check the executable locally - if(!m_Ctx.Replay().CurrentRemote()) + if(!m_Ctx.Replay().CurrentRemote().IsValid()) { if(!QFileInfo::exists(exe)) { @@ -1201,7 +1199,7 @@ void CaptureDialog::TriggerCapture() QString workingDir; // for non-remote captures, check the directory locally - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { workingDir = ui->workDirPath->text(); } diff --git a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp index f21c53a93..d3ac59f89 100644 --- a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp +++ b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp @@ -705,9 +705,7 @@ bool LiveCapture::checkAllowClose() // we either have to save or delete the capture. Make sure that if it's remote that we are able // to by having an active connection or replay context on that host. if(suppressRemoteWarning == false && (!m_Connection || !m_Connection->Connected()) && - !cap->local && (!m_Ctx.Replay().CurrentRemote() || - QString(m_Ctx.Replay().CurrentRemote()->hostname) != m_Hostname || - !m_Ctx.Replay().CurrentRemote()->connected)) + !cap->local && m_Ctx.Replay().CurrentRemote().Hostname() != rdcstr(m_Hostname)) { QMessageBox::StandardButton res2 = RDDialog::question( this, tr("No active replay context"), @@ -752,9 +750,7 @@ void LiveCapture::openCapture(Capture *cap) { cap->opened = true; - if(!cap->local && (!m_Ctx.Replay().CurrentRemote() || - QString(m_Ctx.Replay().CurrentRemote()->hostname) != m_Hostname || - !m_Ctx.Replay().CurrentRemote()->connected)) + if(!cap->local && m_Ctx.Replay().CurrentRemote().Hostname() != rdcstr(m_Hostname)) { RDDialog::critical( this, tr("No active replay context"), @@ -821,9 +817,7 @@ bool LiveCapture::saveCapture(Capture *cap, QString path) } else { - if(!m_Ctx.Replay().CurrentRemote() || - QString(m_Ctx.Replay().CurrentRemote()->hostname) != m_Hostname || - !m_Ctx.Replay().CurrentRemote()->connected) + if(m_Ctx.Replay().CurrentRemote().Hostname() != rdcstr(m_Hostname)) { RDDialog::critical(this, tr("No active replay context"), tr("This capture is on remote host %1 and there is no active replay " @@ -1054,9 +1048,7 @@ void LiveCapture::connectionClosed() // to this machine as a remote context if(!cap->local) { - if(!m_Ctx.Replay().CurrentRemote() || - QString(m_Ctx.Replay().CurrentRemote()->hostname) != m_Hostname || - !m_Ctx.Replay().CurrentRemote()->connected) + if(m_Ctx.Replay().CurrentRemote().Hostname() != rdcstr(m_Hostname)) return; } diff --git a/qrenderdoc/Windows/Dialogs/RemoteManager.cpp b/qrenderdoc/Windows/Dialogs/RemoteManager.cpp index 89de7876b..bfe286f6b 100644 --- a/qrenderdoc/Windows/Dialogs/RemoteManager.cpp +++ b/qrenderdoc/Windows/Dialogs/RemoteManager.cpp @@ -58,25 +58,16 @@ static RemoteConnect getRemoteConnect(RDTreeWidgetItem *item) return item->tag().value(); } -static void setRemoteHost(RDTreeWidgetItem *item, RemoteHost *host) +static void setRemoteHost(RDTreeWidgetItem *item, RemoteHost host) { if(!item) return; - item->setTag(QVariant::fromValue((uintptr_t)host)); -} - -static RemoteHost *getRemoteHost(RDTreeWidgetItem *item) -{ - if(!item) - return NULL; - - return (RemoteHost *)item->tag().value(); + item->setTag(host.Hostname()); } void deleteItemAndHost(RDTreeWidgetItem *item) { - delete getRemoteHost(item); delete item; } @@ -116,7 +107,7 @@ RemoteManager::RemoteManager(ICaptureContext &ctx, MainWindow *main) m_Ctx.Config().AddAndroidHosts(); - for(RemoteHost *h : m_Ctx.Config().RemoteHosts) + for(RemoteHost h : m_Ctx.Config().GetRemoteHosts()) addHost(h); on_hosts_itemSelectionChanged(); @@ -125,10 +116,18 @@ RemoteManager::RemoteManager(ICaptureContext &ctx, MainWindow *main) RemoteManager::~RemoteManager() { for(RDTreeWidgetItem *item : m_QueuedDeletes) - deleteItemAndHost(item); + delete item; delete ui; } +RemoteHost RemoteManager::getRemoteHost(RDTreeWidgetItem *item) +{ + if(!item) + return RemoteHost(); + + return m_Ctx.Config().GetRemoteHost(item->tag().toString()); +} + void RemoteManager::closeWhenFinished() { m_ExternalRef.acquire(1); @@ -137,15 +136,12 @@ void RemoteManager::closeWhenFinished() void RemoteManager::setRemoteServerLive(RDTreeWidgetItem *node, bool live, bool busy) { - RemoteHost *host = getRemoteHost(node); + RemoteHost host = getRemoteHost(node); - if(!host) + if(!host.IsValid()) return; - host->serverRunning = live; - host->busy = busy; - - if(host->IsLocalhost()) + if(host.IsLocalhost()) { node->setIcon(0, QIcon()); node->setText(1, QString()); @@ -154,11 +150,11 @@ void RemoteManager::setRemoteServerLive(RDTreeWidgetItem *node, bool live, bool { QString text = live ? tr("Remote server running") : tr("No remote server"); - if(host->connected) + if(host.IsConnected()) text += tr(" (Active Context)"); - else if(host->versionMismatch) + else if(host.IsVersionMismatch()) text += tr(" (Version Mismatch)"); - else if(host->busy) + else if(host.IsBusy()) text += tr(" (Busy)"); node->setText(1, text); @@ -167,15 +163,9 @@ void RemoteManager::setRemoteServerLive(RDTreeWidgetItem *node, bool live, bool } } -bool RemoteManager::isRemoteServerLive(RDTreeWidgetItem *node) +void RemoteManager::addHost(RemoteHost host) { - RemoteHost *host = getRemoteHost(node); - return host && host->serverRunning; -} - -void RemoteManager::addHost(RemoteHost *host) -{ - RDTreeWidgetItem *node = new RDTreeWidgetItem({host->Name(), lit("...")}); + RDTreeWidgetItem *node = new RDTreeWidgetItem({host.Name(), lit("...")}); node->setItalic(true); node->setIcon(0, Icons::hourglass()); @@ -200,15 +190,15 @@ void RemoteManager::updateLookupsStatus() void RemoteManager::runRemoteServer(RDTreeWidgetItem *node) { - RemoteHost *host = getRemoteHost(node); + RemoteHost host = getRemoteHost(node); - if(!host) + if(!host.IsValid()) { m_Lookups.acquire(); return; } - host->Launch(); + host.Launch(); // now refresh this host refreshHost(node); @@ -218,22 +208,25 @@ void RemoteManager::runRemoteServer(RDTreeWidgetItem *node) void RemoteManager::refreshHost(RDTreeWidgetItem *node) { - RemoteHost *host = getRemoteHost(node); + RemoteHost host = getRemoteHost(node); - if(!host) + if(!host.IsValid()) return; m_Lookups.release(); // this function looks up the remote connections and for each one open // queries it for the API, target (usually executable name) and if any user is already connected - LambdaThread *th = new LambdaThread([this, node, host]() { + LambdaThread *th = new LambdaThread([ this, node, h = host ]() { QByteArray username = GetSystemUsername().toUtf8(); - host->CheckStatus(); + // make a mutable copy and check the status + RemoteHost host = h; + host.CheckStatus(); - GUIInvoke::call( - this, [this, node, host]() { setRemoteServerLive(node, host->serverRunning, host->busy); }); + GUIInvoke::call(this, [this, node, host]() { + setRemoteServerLive(node, host.IsServerRunning(), host.IsBusy()); + }); uint32_t nextIdent = 0; @@ -242,13 +235,13 @@ void RemoteManager::refreshHost(RDTreeWidgetItem *node) // just a sanity check to make sure we don't hit some unexpected case and infinite loop uint32_t prevIdent = nextIdent; - nextIdent = RENDERDOC_EnumerateRemoteTargets(host->hostname.c_str(), nextIdent); + nextIdent = RENDERDOC_EnumerateRemoteTargets(host.Hostname().c_str(), nextIdent); if(nextIdent == 0 || prevIdent >= nextIdent) break; ITargetControl *conn = - RENDERDOC_CreateTargetControl(host->hostname.c_str(), nextIdent, username.data(), false); + RENDERDOC_CreateTargetControl(host.Hostname().c_str(), nextIdent, username.data(), false); if(conn) { @@ -263,7 +256,7 @@ void RemoteManager::refreshHost(RDTreeWidgetItem *node) else running = tr("Running %1").arg(api); - RemoteConnect tag(host->hostname, host->Name(), nextIdent); + RemoteConnect tag(host.Hostname(), host.Name(), nextIdent); GUIInvoke::call(this, [this, node, target, running, tag]() { RDTreeWidgetItem *child = new RDTreeWidgetItem({target, running}); @@ -297,7 +290,7 @@ void RemoteManager::updateStatus() ui->refreshAll->setEnabled(true); for(RDTreeWidgetItem *item : m_QueuedDeletes) - deleteItemAndHost(item); + delete item; m_QueuedDeletes.clear(); // if the external ref is gone now, we can delete ourselves @@ -337,27 +330,27 @@ void RemoteManager::updateConnectButton() ui->connect->setEnabled(true); ui->connect->setText(tr("Connect to App")); - RemoteHost *host = getRemoteHost(item); + RemoteHost host = getRemoteHost(item); - if(host) + if(host.IsValid()) { - if(host->IsLocalhost()) + if(host.IsLocalhost()) { ui->connect->setText(tr("Run Server")); ui->connect->setEnabled(false); } - else if(host->serverRunning) + else if(host.IsServerRunning()) { ui->connect->setText(tr("Shutdown")); - if(host->busy && !host->connected) + if(host.IsBusy() && !host.IsConnected()) ui->connect->setEnabled(false); } else { ui->connect->setText(tr("Run Server")); - if(host->runCommand.isEmpty()) + if(host.RunCommand().isEmpty()) ui->connect->setEnabled(false); } } @@ -375,23 +368,14 @@ void RemoteManager::addNewHost() { bool found = false; - for(int i = 0; i < m_Ctx.Config().RemoteHosts.count(); i++) - { - QString hostname = m_Ctx.Config().RemoteHosts[i]->hostname; - if(hostname.compare(host, Qt::CaseInsensitive) == 0) - { - found = true; - break; - } - } + RemoteHost h = m_Ctx.Config().GetRemoteHost(host); - if(!found) + if(!h.IsValid()) { - RemoteHost *h = new RemoteHost(); - h->hostname = host; - h->runCommand = ui->runCommand->text().trimmed(); + h = RemoteHost((rdcstr)host); + h.SetRunCommand(ui->runCommand->text().trimmed()); - m_Ctx.Config().RemoteHosts.push_back(h); + m_Ctx.Config().AddRemoteHost(h); m_Ctx.Config().Save(); addHost(h); @@ -408,11 +392,11 @@ void RemoteManager::setRunCommand() if(!item) return; - RemoteHost *h = getRemoteHost(item); + RemoteHost h = getRemoteHost(item); - if(h) + if(h.IsValid()) { - h->runCommand = ui->runCommand->text().trimmed(); + h.SetRunCommand(ui->runCommand->text().trimmed()); m_Ctx.Config().Save(); } } @@ -426,7 +410,7 @@ void RemoteManager::queueDelete(RDTreeWidgetItem *item) } else { - deleteItemAndHost(item); + delete item; } } @@ -448,21 +432,21 @@ void RemoteManager::on_hosts_itemSelectionChanged() RDTreeWidgetItem *item = ui->hosts->selectedItem(); - RemoteHost *host = item ? getRemoteHost(item) : NULL; + RemoteHost host = getRemoteHost(item); ui->runCommand->setText(QString()); - if(host) + if(host.IsValid()) { if(ui->refreshAll->isEnabled()) ui->refreshOne->setEnabled(true); - ui->runCommand->setText(host->runCommand); - ui->hostname->setText(host->Name()); + ui->runCommand->setText(host.RunCommand()); + ui->hostname->setText(host.Name()); ui->addUpdateHost->setText(tr("Update")); - if(host->IsLocalhost() || host->IsADB()) + if(host.IsLocalhost() || host.IsADB()) { // localhost and android hosts cannot be updated or have their run command changed ui->addUpdateHost->setEnabled(false); @@ -532,7 +516,7 @@ void RemoteManager::on_runCommand_keyPress(QKeyEvent *event) void RemoteManager::on_addUpdateHost_clicked() { RDTreeWidgetItem *item = ui->hosts->selectedItem(); - if(item && getRemoteHost(item)) + if(getRemoteHost(item).IsValid()) setRunCommand(); else addNewHost(); @@ -589,26 +573,26 @@ void RemoteManager::on_connect_clicked() return; RemoteConnect connect = getRemoteConnect(node); - RemoteHost *host = getRemoteHost(node); + RemoteHost host = getRemoteHost(node); if(connect.ident > 0) { connectToApp(node); } - else if(host) + else if(host.IsValid()) { - if(host->serverRunning) + if(host.IsServerRunning()) { QMessageBox::StandardButton res = RDDialog::question( this, tr("Remote server shutdown"), - tr("Are you sure you wish to shut down running remote server on %1?").arg(host->Name()), + tr("Are you sure you wish to shut down running remote server on %1?").arg(host.Name()), RDDialog::YesNoCancel); if(res == QMessageBox::Cancel || res == QMessageBox::No) return; // shut down - if(host->connected) + if(host.IsConnected()) { m_Ctx.Replay().ShutdownServer(); setRemoteServerLive(node, false, false); @@ -617,7 +601,7 @@ void RemoteManager::on_connect_clicked() { IRemoteServer *server = NULL; ReplayStatus status = - RENDERDOC_CreateRemoteServerConnection(host->hostname.c_str(), 0, &server); + RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), 0, &server); if(server) server->ShutdownServerAndConnection(); setRemoteServerLive(node, false, false); @@ -627,6 +611,14 @@ void RemoteManager::on_connect_clicked() tr("Error shutting down remote server: %1").arg(ToQStr(status))); } + // kick off a thread to check the status + LambdaThread *th = new LambdaThread([ this, h = host ]() { + RemoteHost host = h; + host.CheckStatus(); + }); + th->selfDelete(true); + th->start(); + updateConnectButton(); } else @@ -654,12 +646,12 @@ void RemoteManager::on_deleteHost_clicked() if(!item) return; - RemoteHost *host = getRemoteHost(item); + RemoteHost host = getRemoteHost(item); int itemIdx = ui->hosts->indexOfTopLevelItem(item); // don't delete running instances on a host - if(item->parent() != ui->hosts->invisibleRootItem() || itemIdx < 0 || !host) + if(item->parent() != ui->hosts->invisibleRootItem() || itemIdx < 0 || !host.IsValid()) return; QString hostname = item->text(0); @@ -676,12 +668,12 @@ void RemoteManager::on_deleteHost_clicked() if(res == QMessageBox::Yes) { - int32_t idx = m_Ctx.Config().RemoteHosts.indexOf(host); - if(idx < 0) + RemoteHost h = m_Ctx.Config().GetRemoteHost(host.Hostname()); + if(!h.IsValid()) return; // the host will be removed in queueDelete. - m_Ctx.Config().RemoteHosts.erase(idx); + m_Ctx.Config().RemoveRemoteHost(h); m_Ctx.Config().Save(); item->clear(); diff --git a/qrenderdoc/Windows/Dialogs/RemoteManager.h b/qrenderdoc/Windows/Dialogs/RemoteManager.h index 9a10fd2fa..2c2eed1bf 100644 --- a/qrenderdoc/Windows/Dialogs/RemoteManager.h +++ b/qrenderdoc/Windows/Dialogs/RemoteManager.h @@ -77,12 +77,12 @@ private: QList m_QueuedDeletes; + RemoteHost getRemoteHost(RDTreeWidgetItem *item); void queueDelete(RDTreeWidgetItem *item); - bool isRemoteServerLive(RDTreeWidgetItem *node); void setRemoteServerLive(RDTreeWidgetItem *node, bool live, bool busy); - void addHost(RemoteHost *host); + void addHost(RemoteHost host); void updateLookupsStatus(); void runRemoteServer(RDTreeWidgetItem *node); diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index a5c08ef1b..3740653fd 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -552,9 +552,10 @@ void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, } LiveCapture *live = new LiveCapture( - m_Ctx, m_Ctx.Replay().CurrentRemote() ? m_Ctx.Replay().CurrentRemote()->hostname : "", - m_Ctx.Replay().CurrentRemote() ? m_Ctx.Replay().CurrentRemote()->Name() : "", ret.ident, - this, this); + m_Ctx, + m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Hostname() : "", + m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Name() : "", + ret.ident, this, this); ShowLiveCapture(live); callback(live); }); @@ -639,8 +640,7 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local QString machineIdent; ReplaySupport support = ReplaySupport::Unsupported; - bool remoteReplay = - !local || (m_Ctx.Replay().CurrentRemote() && m_Ctx.Replay().CurrentRemote()->connected); + bool remoteReplay = !local || m_Ctx.Replay().CurrentRemote().IsConnected(); if(local) { @@ -701,8 +701,7 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local [this]() { return contextChooser->isEnabled(); }); } - remoteReplay = - (m_Ctx.Replay().CurrentRemote() && m_Ctx.Replay().CurrentRemote()->connected); + remoteReplay = m_Ctx.Replay().CurrentRemote().IsConnected(); if(!remoteReplay) { @@ -753,7 +752,7 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local QString remoteMessage = tr("This capture was captured with %1 and cannot be replayed on %2.\n\n") .arg(driver) - .arg(m_Ctx.Replay().CurrentRemote()->Name()); + .arg(m_Ctx.Replay().CurrentRemote().Name()); remoteMessage += tr("Try selecting a different remote context in the status bar."); @@ -1005,8 +1004,8 @@ void MainWindow::SetTitle(const QString &filename) prefix += lit(" - "); } - if(m_Ctx.Replay().CurrentRemote()) - prefix += tr("Remote: %1 - ").arg(m_Ctx.Replay().CurrentRemote()->Name()); + if(m_Ctx.Replay().CurrentRemote().IsValid()) + prefix += tr("Remote: %1 - ").arg(m_Ctx.Replay().CurrentRemote().Name()); QString text = prefix + lit("RenderDoc "); @@ -1592,8 +1591,8 @@ void MainWindow::remoteProbe() { QMutexLocker lock(&m_ProbeRemoteHostsLock); m_ProbeRemoteHosts.clear(); - for(RemoteHost *host : m_Ctx.Config().RemoteHosts) - m_ProbeRemoteHosts.push_back(*host); + for(RemoteHost host : m_Ctx.Config().GetRemoteHosts()) + m_ProbeRemoteHosts.push_back(host); } }); @@ -1604,41 +1603,20 @@ void MainWindow::remoteProbe() hosts = m_ProbeRemoteHosts; } - QList> updates; - for(RemoteHost &host : hosts) { // don't mess with a host we're connected to - this is handled anyway - if(host.connected) + if(host.IsConnected()) continue; - RemoteHost updated = host; - - updated.CheckStatus(); + // this will do the bulk of the status checking on this thread without holding any lock, then + // grab the remote host lock and update the config's host (if it's still there) + host.CheckStatus(); // bail as soon as we notice that we're done if(!m_RemoteProbeSemaphore.available()) return; - - if(updated != host) - updates.push_back(qMakePair(host, updated)); } - - GUIInvoke::call(this, [this, updates] { - // this is a bit naive - it suffers from the A-B-A problem where the host could be updated - // then restored to its original state before we notice. In this case though we don't care - // about that, we just don't want to trash some important changes like the connected status - // changing or something. - - for(const QPair &u : updates) - { - for(RemoteHost *host : m_Ctx.Config().RemoteHosts) - { - if(*host == u.first) - *host = u.second; - } - } - }); } } @@ -1661,13 +1639,13 @@ void MainWindow::messageCheck() bool disconnected = false; - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) { - bool wasRunning = m_Ctx.Replay().CurrentRemote()->serverRunning; + bool wasRunning = m_Ctx.Replay().CurrentRemote().IsServerRunning(); m_Ctx.Replay().PingRemote(); - if(wasRunning != m_Ctx.Replay().CurrentRemote()->serverRunning) + if(wasRunning != m_Ctx.Replay().CurrentRemote().IsServerRunning()) { qCritical() << "Remote server disconnected"; disconnected = true; @@ -1692,7 +1670,8 @@ void MainWindow::messageCheck() "RenderDoc to reconnect and load the capture again")); } - if(m_Ctx.Replay().CurrentRemote() && !m_Ctx.Replay().CurrentRemote()->serverRunning) + if(m_Ctx.Replay().CurrentRemote().IsValid() && + !m_Ctx.Replay().CurrentRemote().IsServerRunning()) contextChooser->setIcon(Icons::cross()); if(!msgs.empty()) @@ -1711,11 +1690,11 @@ void MainWindow::messageCheck() } else if(!m_Ctx.IsCaptureLoaded() && !m_Ctx.IsCaptureLoading()) { - if(m_Ctx.Replay().CurrentRemote()) + if(m_Ctx.Replay().CurrentRemote().IsValid()) m_Ctx.Replay().PingRemote(); GUIInvoke::call(this, [this]() { - if(m_Ctx.Replay().CurrentRemote() && !m_Ctx.Replay().CurrentRemote()->serverRunning) + if(m_Ctx.Replay().CurrentRemote().IsValid() && !m_Ctx.Replay().CurrentRemote().IsServerRunning()) { contextChooser->setIcon(Icons::cross()); contextChooser->setText(tr("Replay Context: %1").arg(tr("Local"))); @@ -1735,32 +1714,35 @@ void MainWindow::FillRemotesMenu(QMenu *menu, bool includeLocalhost) { menu->clear(); - for(int i = 0; i < m_Ctx.Config().RemoteHosts.count(); i++) - { - RemoteHost *host = m_Ctx.Config().RemoteHosts[i]; + rdcarray hosts = m_Ctx.Config().GetRemoteHosts(); - // add localhost at the end - if(host->IsLocalhost()) + for(int i = 0; i < hosts.count(); i++) + { + RemoteHost host = hosts[i]; + + // add localhost at the end, skip invalid hosts + if(host.IsLocalhost() || !host.IsValid()) continue; QAction *action = new QAction(menu); - action->setIcon(host->serverRunning && !host->versionMismatch ? Icons::tick() : Icons::cross()); - if(host->connected) - action->setText(tr("%1 (Connected)").arg(host->Name())); - else if(host->serverRunning && host->versionMismatch) - action->setText(tr("%1 (Bad Version)").arg(host->Name())); - else if(host->serverRunning && host->busy) - action->setText(tr("%1 (Busy)").arg(host->Name())); - else if(host->serverRunning) - action->setText(tr("%1 (Online)").arg(host->Name())); + action->setIcon(host.IsServerRunning() && !host.IsVersionMismatch() ? Icons::tick() + : Icons::cross()); + if(host.IsConnected()) + action->setText(tr("%1 (Connected)").arg(host.Name())); + else if(host.IsServerRunning() && host.IsVersionMismatch()) + action->setText(tr("%1 (Bad Version)").arg(host.Name())); + else if(host.IsServerRunning() && host.IsBusy()) + action->setText(tr("%1 (Busy)").arg(host.Name())); + else if(host.IsServerRunning()) + action->setText(tr("%1 (Online)").arg(host.Name())); else - action->setText(tr("%1 (Offline)").arg(host->Name())); + action->setText(tr("%1 (Offline)").arg(host.Name())); QObject::connect(action, &QAction::triggered, this, &MainWindow::switchContext); action->setData(i); // don't allow switching to the connected host - if(host->connected) + if(host.IsConnected()) action->setEnabled(false); menu->addAction(action); @@ -1782,18 +1764,18 @@ void MainWindow::FillRemotesMenu(QMenu *menu, bool includeLocalhost) void MainWindow::setRemoteHost(int hostIdx) { - RemoteHost *host = NULL; - if(hostIdx >= 0 && hostIdx < m_Ctx.Config().RemoteHosts.count()) - { - host = m_Ctx.Config().RemoteHosts[hostIdx]; - } + rdcarray hosts = m_Ctx.Config().GetRemoteHosts(); + + RemoteHost host; + if(hostIdx >= 0 && hostIdx < hosts.count()) + host = hosts[hostIdx]; for(LiveCapture *live : m_LiveCaptures) { // allow live captures to this host to stay open, that way // we can connect to a live capture, then switch into that // context - if(host && live->hostname() == host->hostname) + if(host.IsValid() && live->hostname() == host.Hostname()) continue; if(!live->checkAllowClose()) @@ -1808,7 +1790,7 @@ void MainWindow::setRemoteHost(int hostIdx) // allow live captures to this host to stay open, that way // we can connect to a live capture, then switch into that // context - if(host && live->hostname() == host->hostname) + if(host.IsValid() && live->hostname() == host.Hostname()) continue; live->cleanItems(); @@ -1817,7 +1799,7 @@ void MainWindow::setRemoteHost(int hostIdx) m_Ctx.Replay().DisconnectFromRemoteServer(); - if(!host) + if(!host.IsValid()) { contextChooser->setIcon(Icons::house()); contextChooser->setText(tr("Replay Context: %1").arg(tr("Local"))); @@ -1833,8 +1815,8 @@ void MainWindow::setRemoteHost(int hostIdx) } else { - contextChooser->setText(tr("Replay Context: %1").arg(host->Name())); - contextChooser->setIcon(host->serverRunning ? Icons::connect() : Icons::disconnect()); + contextChooser->setText(tr("Replay Context: %1").arg(host.Name())); + contextChooser->setIcon(host.IsServerRunning() ? Icons::connect() : Icons::disconnect()); // disable until checking is done contextChooser->setEnabled(false); @@ -1845,11 +1827,12 @@ void MainWindow::setRemoteHost(int hostIdx) statusText->setText(tr("Checking remote server status...")); - LambdaThread *th = new LambdaThread([this, host]() { - // see if the server is up - host->CheckStatus(); + LambdaThread *th = new LambdaThread([ this, h = host ]() { + // make a mutable copy and see if the server is up + RemoteHost host = h; + host.CheckStatus(); - if(host->IsADB() && !RENDERDOC_IsAndroidSupported(host->hostname.c_str())) + if(host.IsADB() && !RENDERDOC_IsAndroidSupported(host.Hostname().c_str())) { // check to see if we should warn the user about this unsupported android version. GUIInvoke::call(this, [this]() { @@ -1869,7 +1852,7 @@ void MainWindow::setRemoteHost(int hostIdx) }); } - if(!host->serverRunning && !host->runCommand.isEmpty()) + if(!host.IsServerRunning() && !host.RunCommand().isEmpty()) { GUIInvoke::call(this, [this]() { statusText->setText(tr("Running remote server command...")); @@ -1877,28 +1860,28 @@ void MainWindow::setRemoteHost(int hostIdx) statusProgress->setMaximum(0); }); - ReplayStatus launchStatus = host->Launch(); + ReplayStatus launchStatus = host.Launch(); if(launchStatus != ReplayStatus::Succeeded) { showLaunchError(launchStatus); } // check if it's running now - host->CheckStatus(); + host.CheckStatus(); GUIInvoke::call(this, [this]() { statusProgress->setVisible(false); }); } ReplayStatus status = ReplayStatus::Succeeded; - if(host->serverRunning && !host->busy) + if(host.IsServerRunning() && !host.IsBusy()) { status = m_Ctx.Replay().ConnectToRemoteServer(host); } GUIInvoke::call(this, [this, host, status]() { - contextChooser->setIcon(host->serverRunning && !host->busy ? Icons::connect() - : Icons::disconnect()); + contextChooser->setIcon(host.IsServerRunning() && !host.IsBusy() ? Icons::connect() + : Icons::disconnect()); if(status != ReplayStatus::Succeeded) { @@ -1906,22 +1889,22 @@ void MainWindow::setRemoteHost(int hostIdx) contextChooser->setText(tr("Replay Context: %1").arg(tr("Local"))); statusText->setText(tr("Connection failed: %1").arg(ToQStr(status))); } - else if(host->versionMismatch) + else if(host.IsVersionMismatch()) { statusText->setText( tr("Remote server is not running RenderDoc %1").arg(lit(FULL_VERSION_STRING))); } - else if(host->busy) + else if(host.IsBusy()) { statusText->setText(tr("Remote server in use elsewhere")); } - else if(host->serverRunning) + else if(host.IsServerRunning()) { statusText->setText(tr("Remote server ready")); } else { - if(!host->runCommand.isEmpty()) + if(!host.RunCommand().isEmpty()) statusText->setText(tr("Remote server not running or failed to start")); else statusText->setText(tr("Remote server not running - no start command configured")); @@ -2030,7 +2013,7 @@ void MainWindow::OnCaptureClosed() SetTitle(); // if the remote sever disconnected during capture replay, resort back to a 'disconnected' state - if(m_Ctx.Replay().CurrentRemote() && !m_Ctx.Replay().CurrentRemote()->serverRunning) + if(m_Ctx.Replay().CurrentRemote().IsValid() && !m_Ctx.Replay().CurrentRemote().IsServerRunning()) { statusText->setText( tr("Remote server disconnected. To attempt to reconnect please select it again.")); @@ -2914,10 +2897,10 @@ void MainWindow::showLaunchError(ReplayStatus status) bool MainWindow::isCapturableAppRunningOnAndroid() { - if(!m_Ctx.Replay().CurrentRemote() || !m_Ctx.Replay().CurrentRemote()->IsADB()) + if(!m_Ctx.Replay().CurrentRemote().IsADB()) return false; - rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname; + rdcstr host = m_Ctx.Replay().CurrentRemote().Hostname(); uint32_t ident = RENDERDOC_EnumerateRemoteTargets(host.c_str(), 0); return ident != 0; }