mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Remote, MG_Test, scripts] (P12): the server's own window - ServerDisplay (an opaque window with hooks, a lease, a bounded Detach), the ServerOwned arm with the one-mode-per-session latch and the named no-display refusal, the lost-window release on the apply thread and the ServerWindowLost family
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Server/ServerDisplay.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// P12 (on-screen server window), D3/D6. See ServerDisplay.h.
|
||||
|
||||
#include "ServerDisplay.h"
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/native_window.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
namespace {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
// How often a waiting AcquireFor re-asks its cancel predicate. The predicate is the apply
|
||||
// thread's own stop / latch state, which nobody signals through this object's condition.
|
||||
constexpr auto kCancelPollInterval = std::chrono::milliseconds(50);
|
||||
} // namespace
|
||||
|
||||
const char* ServerWindowAcquireName(ServerWindowAcquire result) {
|
||||
switch (result) {
|
||||
case ServerWindowAcquire::Acquired: return "Acquired";
|
||||
case ServerWindowAcquire::NoDisplay: return "NoDisplay";
|
||||
case ServerWindowAcquire::NoWindow: return "NoWindow";
|
||||
case ServerWindowAcquire::Cancelled: return "Cancelled";
|
||||
case ServerWindowAcquire::Interrupted: return "Interrupted";
|
||||
case ServerWindowAcquire::LeasedElsewhere: return "LeasedElsewhere";
|
||||
}
|
||||
return "<unknown ServerWindowAcquire>";
|
||||
}
|
||||
|
||||
const char* ServerWindowDetachName(ServerWindowDetach result) {
|
||||
switch (result) {
|
||||
case ServerWindowDetach::NoWindow: return "NoWindow";
|
||||
case ServerWindowDetach::Released: return "Released";
|
||||
case ServerWindowDetach::ReleasedBySession: return "ReleasedBySession";
|
||||
case ServerWindowDetach::TimedOut: return "TimedOut";
|
||||
}
|
||||
return "<unknown ServerWindowDetach>";
|
||||
}
|
||||
|
||||
void ServerDisplay::ReleaseOutsideLock(const ServerDisplayHooks& hooks, void* window) const {
|
||||
if (window != nullptr && hooks.release != nullptr) hooks.release(hooks.user, window);
|
||||
}
|
||||
|
||||
void ServerDisplay::Install(const ServerDisplayHooks& hooks) {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_hooks = hooks;
|
||||
m_installed = true;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
MGLOG_I("MG_Remote server: this process owns a display (ServerDisplay installed); a ServerOwned "
|
||||
"window surface is served on its window");
|
||||
}
|
||||
|
||||
void ServerDisplay::Uninstall(Uint32 detachTimeoutMs) {
|
||||
(void)Detach(detachTimeoutMs);
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_installed = false;
|
||||
// The hooks stay until every reference they took is released: a timed-out Detach may
|
||||
// still owe EndLease a release through them.
|
||||
if (m_releaseAfterLease.empty()) m_hooks = ServerDisplayHooks{};
|
||||
}
|
||||
m_cv.notify_all();
|
||||
MGLOG_I("MG_Remote server: ServerDisplay uninstalled; this process owns no display");
|
||||
}
|
||||
|
||||
Bool ServerDisplay::HasDisplay() const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_installed;
|
||||
}
|
||||
|
||||
void ServerDisplay::Attach(void* window, Uint32 width, Uint32 height) {
|
||||
if (window == nullptr) return;
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (window == m_window) {
|
||||
// surfaceChanged: the same window at a new extent. This is also how a geometry request
|
||||
// is answered, so a waiting AcquireFor is woken.
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
lock.unlock();
|
||||
m_cv.notify_all();
|
||||
MGLOG_I("MG_Remote server: server window %p is now %ux%u", window, width, height);
|
||||
return;
|
||||
}
|
||||
if (m_window != nullptr) {
|
||||
// A new window while one is still attached - the platform skipped the destroy. The old
|
||||
// one is detached exactly as surfaceDestroyed would have done it, bounded.
|
||||
lock.unlock();
|
||||
const ServerWindowDetach detached = Detach(kDefaultDetachTimeoutMs);
|
||||
MGLOG_W("MG_Remote server: server window %p attached while another was still attached; the "
|
||||
"old one was detached first (%s)",
|
||||
window, ServerWindowDetachName(detached));
|
||||
lock.lock();
|
||||
}
|
||||
if (m_hooks.acquire != nullptr) m_hooks.acquire(m_hooks.user, window);
|
||||
m_window = window;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
++m_generation;
|
||||
const Uint64 generation = m_generation;
|
||||
lock.unlock();
|
||||
m_cv.notify_all();
|
||||
MGLOG_I("MG_Remote server: server window %p attached at %ux%u (generation %llu)", window, width, height,
|
||||
static_cast<unsigned long long>(generation));
|
||||
}
|
||||
|
||||
ServerWindowDetach ServerDisplay::Detach(Uint32 timeoutMs) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (m_window == nullptr) return ServerWindowDetach::NoWindow;
|
||||
void* const window = m_window;
|
||||
m_window = nullptr;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
++m_generation;
|
||||
ServerWindowDetach result = ServerWindowDetach::Released;
|
||||
if (m_leaseHolder != nullptr) {
|
||||
// A live on-screen session renders into this window. Ask it to let go - the hook runs
|
||||
// under this lock, so the holder cannot end its lease (and go away) between being read
|
||||
// here and being called - then wait for EndLease, bounded.
|
||||
if (!m_lostRequested) {
|
||||
m_lostRequested = true;
|
||||
if (m_onLost != nullptr) m_onLost(m_leaseHolder);
|
||||
}
|
||||
const Bool released = m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs),
|
||||
[this] { return m_leaseHolder == nullptr; });
|
||||
if (!released) {
|
||||
// NOT RELEASED UNDER IT. The holder may still reference the window (Magma keeps the
|
||||
// raw pointer until the swapchain is built), so the reference stays until it ends
|
||||
// its lease. The UI thread is not held past its bound either way.
|
||||
m_releaseAfterLease.push_back(window);
|
||||
lock.unlock();
|
||||
m_cv.notify_all();
|
||||
MGLOG_E("MG_Remote server: surfaceDestroyed - the on-screen session did not release server "
|
||||
"window %p within %u ms; its reference is kept until it does (Detach TimedOut)",
|
||||
window, timeoutMs);
|
||||
return ServerWindowDetach::TimedOut;
|
||||
}
|
||||
result = ServerWindowDetach::ReleasedBySession;
|
||||
}
|
||||
const ServerDisplayHooks hooks = m_hooks;
|
||||
lock.unlock();
|
||||
m_cv.notify_all();
|
||||
ReleaseOutsideLock(hooks, window);
|
||||
MGLOG_I("MG_Remote server: server window %p detached (%s)", window, ServerWindowDetachName(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
ServerWindowAcquire ServerDisplay::AcquireFor(Uint32 width, Uint32 height, Uint32 timeoutMs, void* holder,
|
||||
ServerWindowLostHook onLost, ServerWindowWaitCancel cancel,
|
||||
void* cancelUser, ServerWindowLease* out) {
|
||||
ServerDisplayHooks hooks{};
|
||||
Uint64 interrupts = 0;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_installed) return ServerWindowAcquire::NoDisplay;
|
||||
if (m_leaseHolder != nullptr && m_leaseHolder != holder) return ServerWindowAcquire::LeasedElsewhere;
|
||||
hooks = m_hooks;
|
||||
interrupts = m_interrupts;
|
||||
}
|
||||
// The geometry request is an up-call into the platform (Java), made without the lock: its
|
||||
// answer comes back as an Attach() of the same window, which needs the lock.
|
||||
if (hooks.requestGeometry != nullptr) hooks.requestGeometry(hooks.user, width, height);
|
||||
const Bool sizeRequested = width != 0 && height != 0;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
const auto deadline = Clock::now() + std::chrono::milliseconds(timeoutMs);
|
||||
Bool attachedSeen = false;
|
||||
auto geometryDeadline = deadline;
|
||||
for (;;) {
|
||||
if (!m_installed) return ServerWindowAcquire::NoDisplay;
|
||||
if (m_interrupts != interrupts) return ServerWindowAcquire::Interrupted;
|
||||
if (cancel != nullptr && cancel(cancelUser)) return ServerWindowAcquire::Cancelled;
|
||||
if (m_leaseHolder != nullptr && m_leaseHolder != holder) return ServerWindowAcquire::LeasedElsewhere;
|
||||
const auto now = Clock::now();
|
||||
if (m_window != nullptr) {
|
||||
if (!attachedSeen) {
|
||||
attachedSeen = true;
|
||||
geometryDeadline = std::min(deadline, now + std::chrono::milliseconds(kGeometryGraceMs));
|
||||
}
|
||||
const Bool sizeOk = !sizeRequested || (m_width == width && m_height == height);
|
||||
if (sizeOk || now >= geometryDeadline) {
|
||||
m_leaseHolder = holder;
|
||||
m_onLost = onLost;
|
||||
m_lostRequested = false;
|
||||
if (out != nullptr) {
|
||||
out->window = m_window;
|
||||
out->width = m_width;
|
||||
out->height = m_height;
|
||||
out->generation = m_generation;
|
||||
out->sizeAsRequested = sizeOk;
|
||||
}
|
||||
if (!sizeOk) {
|
||||
MGLOG_W("MG_Remote server: the server window is %ux%u, not the %ux%u requested, after "
|
||||
"%u ms of geometry grace; the surface is created at the window's size and the "
|
||||
"client is told so",
|
||||
m_width, m_height, width, height, kGeometryGraceMs);
|
||||
}
|
||||
return ServerWindowAcquire::Acquired;
|
||||
}
|
||||
} else {
|
||||
attachedSeen = false;
|
||||
geometryDeadline = deadline;
|
||||
}
|
||||
if (now >= deadline) return ServerWindowAcquire::NoWindow;
|
||||
const auto wake = std::min({deadline, geometryDeadline, now + kCancelPollInterval});
|
||||
m_cv.wait_until(lock, wake);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerDisplay::EndLease(void* holder) {
|
||||
std::vector<void*> releases;
|
||||
ServerDisplayHooks hooks{};
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (holder == nullptr || m_leaseHolder != holder) return;
|
||||
m_leaseHolder = nullptr;
|
||||
m_onLost = nullptr;
|
||||
m_lostRequested = false;
|
||||
releases.swap(m_releaseAfterLease);
|
||||
hooks = m_hooks;
|
||||
// An Uninstall that ran while this lease still owed releases kept the hooks for them.
|
||||
if (!m_installed) m_hooks = ServerDisplayHooks{};
|
||||
}
|
||||
m_cv.notify_all();
|
||||
for (void* window : releases) {
|
||||
ReleaseOutsideLock(hooks, window);
|
||||
MGLOG_I("MG_Remote server: server window %p released after its session let go (a late Detach)",
|
||||
window);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerDisplay::Interrupt() {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_interrupts;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
Bool ServerDisplay::Attached() const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_window != nullptr;
|
||||
}
|
||||
|
||||
Bool ServerDisplay::Leased() const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_leaseHolder != nullptr;
|
||||
}
|
||||
|
||||
Uint64 ServerDisplay::Generation() const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_generation;
|
||||
}
|
||||
|
||||
ServerDisplay& ServerDisplayInstance() {
|
||||
// ID-8: leak at exit, like every MG_Remote singleton.
|
||||
static ServerDisplay& instance = *new ServerDisplay{};
|
||||
return instance;
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
namespace {
|
||||
void AcquireAndroidWindow(void*, void* window) {
|
||||
ANativeWindow_acquire(static_cast<ANativeWindow*>(window));
|
||||
}
|
||||
void ReleaseAndroidWindow(void*, void* window) {
|
||||
ANativeWindow_release(static_cast<ANativeWindow*>(window));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ServerDisplayHooks AndroidNativeWindowHooks(void (*requestGeometry)(void* user, Uint32 width, Uint32 height),
|
||||
void* user) {
|
||||
ServerDisplayHooks hooks;
|
||||
hooks.acquire = &AcquireAndroidWindow;
|
||||
hooks.release = &ReleaseAndroidWindow;
|
||||
hooks.requestGeometry = requestGeometry;
|
||||
hooks.user = user;
|
||||
return hooks;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Server
|
||||
Reference in New Issue
Block a user