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,