diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj
index c93a4492..1a702282 100644
--- a/idd/LGIdd/LGIdd.vcxproj
+++ b/idd/LGIdd/LGIdd.vcxproj
@@ -60,6 +60,7 @@
+
@@ -106,6 +107,7 @@
+
diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters
index 698960dd..d9268ee3 100644
--- a/idd/LGIdd/LGIdd.vcxproj.filters
+++ b/idd/LGIdd/LGIdd.vcxproj.filters
@@ -154,6 +154,9 @@
Post-processing\Effects
+
+ Transport
+
Transport
@@ -291,6 +294,9 @@
Post-processing\Effects
+
+ Transport
+
Transport
diff --git a/idd/LGIdd/display/CDeviceContext.cpp b/idd/LGIdd/display/CDeviceContext.cpp
index 7cb0d6ee..c5a154e7 100644
--- a/idd/LGIdd/display/CDeviceContext.cpp
+++ b/idd/LGIdd/display/CDeviceContext.cpp
@@ -64,8 +64,8 @@ CDeviceContext::~CDeviceContext()
m_transportTimer = nullptr;
}
- if (m_transport && m_transport->Input())
- m_transport->Input()->Stop();
+ if (m_transport)
+ m_transport->Stop();
}
void CDeviceContext::QueryIddCxCapabilities()
diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp
new file mode 100644
index 00000000..686e579f
--- /dev/null
+++ b/idd/LGIdd/transport/CTransportManager.cpp
@@ -0,0 +1,308 @@
+/**
+ * Looking Glass
+ * Copyright © 2017-2026 The Looking Glass Authors
+ * https://looking-glass.io
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "transport/CTransportManager.h"
+
+#include "CDebug.h"
+
+#include
+#include
+
+static const uint64_t RETRY_DELAY_MS = 500;
+
+CTransportManager::~CTransportManager()
+{
+ Stop();
+}
+
+bool CTransportManager::Add(BackendId id, const char * name, bool required,
+ bool primary, CreateFn create)
+{
+ if (!id || !name || !create || (primary && m_primary))
+ return false;
+
+ for (const auto& current : m_entries)
+ if (current->id == id)
+ return false;
+
+ std::unique_ptr entry(new (std::nothrow) Entry);
+ if (!entry)
+ return false;
+
+ entry->id = id;
+ entry->name = name;
+ entry->required = required;
+ entry->primary = primary;
+ entry->create = create;
+
+ Entry * raw = entry.get();
+ m_entries.push_back(std::move(entry));
+ if (primary)
+ m_primary = raw;
+ return true;
+}
+
+ITransport::OpenResult CTransportManager::OpenEntry(Entry& entry)
+{
+ if (!entry.transport)
+ entry.transport = entry.create();
+ if (!entry.transport)
+ {
+ entry.state = State::FAILED;
+ return OpenResult::FAILURE;
+ }
+
+ const OpenResult result = entry.transport->Open();
+ switch (result)
+ {
+ case OpenResult::SUCCESS:
+ entry.state = State::OPEN;
+ break;
+
+ case OpenResult::RETRY:
+ ScheduleRetry(entry);
+ break;
+
+ case OpenResult::FAILURE:
+ entry.state = State::FAILED;
+ break;
+ }
+ return result;
+}
+
+bool CTransportManager::InitializeEntry(Entry& entry)
+{
+ if (entry.state == State::INITIALIZED || entry.state == State::READY)
+ return true;
+ if (entry.state != State::OPEN || !entry.transport->Initialize())
+ {
+ entry.state = State::FAILED;
+ return false;
+ }
+
+ entry.state = State::INITIALIZED;
+ return true;
+}
+
+bool CTransportManager::SetupEntry(Entry& entry)
+{
+ if (entry.state == State::READY)
+ return true;
+ if (entry.state != State::INITIALIZED ||
+ !entry.transport->Setup(m_alignment))
+ {
+ entry.state = State::FAILED;
+ return false;
+ }
+
+ entry.state = State::READY;
+ return true;
+}
+
+void CTransportManager::ScheduleRetry(Entry& entry)
+{
+ entry.state = State::RETRY;
+ entry.retryAt = GetTickCount64() + RETRY_DELAY_MS;
+}
+
+ITransport::OpenResult CTransportManager::Open()
+{
+ if (!m_primary)
+ return OpenResult::FAILURE;
+
+ OpenResult aggregate = OpenResult::SUCCESS;
+ for (const auto& current : m_entries)
+ {
+ Entry& entry = *current;
+ if (entry.state == State::OPEN ||
+ entry.state == State::INITIALIZED || entry.state == State::READY)
+ continue;
+
+ const OpenResult result = OpenEntry(entry);
+ if (!entry.required || result == OpenResult::SUCCESS)
+ continue;
+ if (result == OpenResult::FAILURE)
+ return OpenResult::FAILURE;
+ aggregate = OpenResult::RETRY;
+ }
+ return aggregate;
+}
+
+bool CTransportManager::Initialize()
+{
+ for (const auto& current : m_entries)
+ {
+ Entry& entry = *current;
+ if (entry.state != State::OPEN)
+ continue;
+ if (!InitializeEntry(entry))
+ {
+ if (entry.required)
+ return false;
+ ScheduleRetry(entry);
+ }
+ }
+
+ m_initialized = true;
+ return m_primary &&
+ (m_primary->state == State::INITIALIZED ||
+ m_primary->state == State::READY);
+}
+
+bool CTransportManager::Setup(size_t alignment)
+{
+ if (!m_initialized || !m_primary)
+ return false;
+
+ m_alignment = alignment;
+ if (!SetupEntry(*m_primary))
+ return false;
+
+ m_setup = true;
+ return true;
+}
+
+void CTransportManager::RetryEntry(Entry& entry, uint64_t now)
+{
+ if (entry.state != State::RETRY || now < entry.retryAt)
+ return;
+
+ if (entry.primary && m_exposed)
+ return;
+
+ if (entry.transport)
+ entry.transport->Stop();
+ entry.transport.reset();
+ ++entry.epoch;
+ if (!entry.epoch)
+ ++entry.epoch;
+
+ if (OpenEntry(entry) != OpenResult::SUCCESS)
+ return;
+ if (m_initialized && !InitializeEntry(entry))
+ {
+ ScheduleRetry(entry);
+ return;
+ }
+ if (m_setup && entry.primary && !SetupEntry(entry))
+ ScheduleRetry(entry);
+}
+
+void CTransportManager::HandleProcessResult(
+ Entry& entry, ProcessResult result)
+{
+ if (result == ProcessResult::OK)
+ return;
+
+ if (entry.primary && m_exposed)
+ {
+ DEBUG_WARN("Transport %s requested a restart while its frame interfaces "
+ "are active", entry.name);
+ return;
+ }
+
+ if (result == ProcessResult::RETRY || !entry.required)
+ {
+ ScheduleRetry(entry);
+ return;
+ }
+
+ entry.state = State::FAILED;
+}
+
+ITransport::ProcessResult CTransportManager::Process(
+ ITransportEvents& events)
+{
+ const uint64_t now = GetTickCount64();
+ for (const auto& current : m_entries)
+ {
+ Entry& entry = *current;
+ RetryEntry(entry, now);
+ if (entry.state != State::INITIALIZED && entry.state != State::READY)
+ continue;
+
+ const ProcessResult result = entry.transport->Process(events);
+ HandleProcessResult(entry, result);
+ }
+ return ProcessResult::OK;
+}
+
+void CTransportManager::Stop()
+{
+ for (auto current = m_entries.rbegin(); current != m_entries.rend();
+ ++current)
+ {
+ Entry& entry = **current;
+ if (entry.transport && entry.state != State::STOPPED)
+ entry.transport->Stop();
+ entry.state = State::STOPPED;
+ }
+}
+
+void CTransportManager::SyncRecovery()
+{
+ for (const auto& current : m_entries)
+ if (current->transport &&
+ (current->state == State::INITIALIZED ||
+ current->state == State::READY))
+ current->transport->SyncRecovery();
+}
+
+void CTransportManager::RecoveryStatus(uint64_t session, uint32_t serial,
+ bool active, Recovery state, uint32_t error)
+{
+ for (const auto& current : m_entries)
+ if (current->transport &&
+ (current->state == State::INITIALIZED ||
+ current->state == State::READY))
+ current->transport->RecoveryStatus(
+ session, serial, active, state, error);
+}
+
+ITransport& CTransportManager::Primary()
+{
+ m_exposed = true;
+ return *m_primary->transport;
+}
+
+FrameMemoryLimits CTransportManager::GetMemoryLimits() const
+{
+ return m_primary->transport->GetMemoryLimits();
+}
+
+DirectFrameBufferMemory CTransportManager::GetDirectMemory() const
+{
+ return m_primary->transport->GetDirectMemory();
+}
+
+IFrameTransport& CTransportManager::Frames()
+{
+ return Primary().Frames();
+}
+
+IControlTransport& CTransportManager::Control()
+{
+ return Primary().Control();
+}
+
+IInputTransport * CTransportManager::Input()
+{
+ return Primary().Input();
+}
diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h
new file mode 100644
index 00000000..8710f20a
--- /dev/null
+++ b/idd/LGIdd/transport/CTransportManager.h
@@ -0,0 +1,98 @@
+/**
+ * Looking Glass
+ * Copyright © 2017-2026 The Looking Glass Authors
+ * https://looking-glass.io
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#pragma once
+
+#include "transport/ITransport.h"
+
+#include
+#include
+
+class CTransportManager final : public ITransport
+{
+public:
+ using CreateFn = std::unique_ptr (*)();
+
+private:
+ enum class State
+ {
+ CLOSED,
+ OPEN,
+ INITIALIZED,
+ READY,
+ RETRY,
+ FAILED,
+ STOPPED,
+ };
+
+ struct Entry
+ {
+ BackendId id;
+ const char * name;
+ bool required;
+ bool primary;
+ CreateFn create;
+ std::unique_ptr transport;
+ State state = State::CLOSED;
+ uint32_t epoch = 1;
+ uint64_t retryAt = 0;
+ };
+
+ std::vector> m_entries;
+ Entry * m_primary = nullptr;
+ bool m_initialized = false;
+ bool m_setup = false;
+ bool m_exposed = false;
+ size_t m_alignment = 0;
+
+ OpenResult OpenEntry(Entry& entry);
+ bool InitializeEntry(Entry& entry);
+ bool SetupEntry(Entry& entry);
+ void RetryEntry(Entry& entry, uint64_t now);
+ void HandleProcessResult(Entry& entry, ProcessResult result);
+ void ScheduleRetry(Entry& entry);
+ ITransport& Primary();
+
+public:
+ CTransportManager() = default;
+ ~CTransportManager() override;
+
+ CTransportManager(const CTransportManager&) = delete;
+ CTransportManager& operator=(const CTransportManager&) = delete;
+
+ bool Add(BackendId id, const char * name, bool required, bool primary,
+ CreateFn create);
+
+ OpenResult Open() override;
+ bool Initialize() override;
+ bool Setup(size_t alignment) override;
+ ProcessResult Process(ITransportEvents& events) override;
+ void Stop() override;
+ void SyncRecovery() override;
+ void RecoveryStatus(uint64_t session, uint32_t serial, bool active,
+ Recovery state, uint32_t error) override;
+
+ FrameMemoryLimits GetMemoryLimits() const override;
+ DirectFrameBufferMemory GetDirectMemory() const override;
+
+ IFrameTransport& Frames() override;
+ IControlTransport& Control() override;
+ IInputTransport * Input() override;
+};
diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h
index e38b0536..4a3bcf3f 100644
--- a/idd/LGIdd/transport/ITransport.h
+++ b/idd/LGIdd/transport/ITransport.h
@@ -44,6 +44,8 @@ public:
class ITransport
{
public:
+ using BackendId = uint32_t;
+
enum class OpenResult
{
SUCCESS,
@@ -51,6 +53,13 @@ public:
FAILURE,
};
+ enum class ProcessResult
+ {
+ OK,
+ RETRY,
+ FAILURE,
+ };
+
enum class Recovery
{
NORMAL,
@@ -63,7 +72,8 @@ public:
virtual OpenResult Open() = 0;
virtual bool Initialize() = 0;
virtual bool Setup(size_t alignment) = 0;
- virtual void Process(ITransportEvents& events) = 0;
+ virtual ProcessResult Process(ITransportEvents& events) = 0;
+ virtual void Stop() = 0;
virtual void SyncRecovery() {}
virtual void RecoveryStatus(
uint64_t, uint32_t, bool, Recovery, uint32_t) {}
diff --git a/idd/LGIdd/transport/TransportFactory.cpp b/idd/LGIdd/transport/TransportFactory.cpp
index ec671ef6..b0e7b5d9 100644
--- a/idd/LGIdd/transport/TransportFactory.cpp
+++ b/idd/LGIdd/transport/TransportFactory.cpp
@@ -20,11 +20,21 @@
#include "transport/TransportFactory.h"
+#include "transport/CTransportManager.h"
#include "transport/lgmp/CLGMPTransport.h"
#include
-std::unique_ptr CreateTransport()
+static std::unique_ptr CreateLGMP()
{
return std::unique_ptr(new (std::nothrow) CLGMPTransport());
}
+
+std::unique_ptr CreateTransport()
+{
+ std::unique_ptr manager(
+ new (std::nothrow) CTransportManager());
+ if (!manager || !manager->Add(1, "LGMP", true, true, CreateLGMP))
+ return std::unique_ptr();
+ return std::unique_ptr(manager.release());
+}
diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
index a6756633..965c1917 100644
--- a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
+++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
@@ -93,7 +93,7 @@ bool CLGMPTransport::Setup(size_t alignment)
return true;
}
-void CLGMPTransport::Process(ITransportEvents& events)
+ITransport::ProcessResult CLGMPTransport::Process(ITransportEvents& events)
{
const CRecovery::Request recovery = m_recovery.Process();
if (recovery.valid)
@@ -104,7 +104,7 @@ void CLGMPTransport::Process(ITransportEvents& events)
// only the protocol-independent recovery channel. This preserves the old
// transport startup boundary while keeping recovery available immediately.
if (!m_ready.load(std::memory_order_acquire))
- return;
+ return ProcessResult::OK;
const LGMP_STATUS processStatus = m_host.Process();
if (processStatus != LGMP_OK)
@@ -113,14 +113,12 @@ void CLGMPTransport::Process(ITransportEvents& events)
{
DEBUG_WARN(
"LGMP reported the shared memory has been corrupted, attempting to recover\n");
- // TODO: reinitialize LGMP.
- return;
+ return ProcessResult::RETRY;
}
DEBUG_ERROR("lgmpHostProcess Failed: %s",
lgmpStatusString(processStatus));
- // TODO: shut down LGMP.
- return;
+ return ProcessResult::FAILURE;
}
const uint64_t now = CFrameScheduler::Nanotime();
@@ -191,6 +189,14 @@ void CLGMPTransport::Process(ITransportEvents& events)
if (m_control.HasNewSubscribers())
m_control.ResendState();
+
+ return ProcessResult::OK;
+}
+
+void CLGMPTransport::Stop()
+{
+ m_ready.store(false, std::memory_order_release);
+ m_input.Stop();
}
void CLGMPTransport::SyncRecovery()
diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.h b/idd/LGIdd/transport/lgmp/CLGMPTransport.h
index 5d97f361..36f3656e 100644
--- a/idd/LGIdd/transport/lgmp/CLGMPTransport.h
+++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.h
@@ -53,7 +53,8 @@ public:
OpenResult Open() override;
bool Initialize() override;
bool Setup(size_t alignment) override;
- void Process(ITransportEvents& events) override;
+ ProcessResult Process(ITransportEvents& events) override;
+ void Stop() override;
void SyncRecovery() override;
void RecoveryStatus(
uint64_t session, uint32_t serial, bool active,