mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
The twelve Server* forwarders' one-slot mailbox carried a raw function
pointer plus a void* to a stack-local Args struct (P5c audit row G4);
neither means anything across a process boundary. The slot now carries
one SurfaceControlFrame by value: kind is the op, scalars are the
arguments, and ok/eglMajor/eglMinor come back in the same value. The
blocking handshake, the shadow, the doorbell order and the C2 exit
block are unchanged.
- protocol.fbs: append-only - SurfaceOpKind gains SetSwapInterval=8,
ReleaseResources=9, SetWindowHandle=10; WindowKind gains MetalLayer=6;
SurfaceOp gains readSurface and context (the MakeCurrent four-tuple).
protocol_generated.h regenerated with the pinned flatc.
- Server/SurfaceControlFrame.{h,cpp}: the frame (trivially copyable by
static_assert) and the op enum - ten wire kinds plus four inproc-only
kinds (InitCapabilities is answered by CapsSnapshot on the wire,
SwapBuffers/InitWindowSurface have no production caller - f0-egl F8,
kept for the inproc test lane).
- Protocol/SurfaceOpCodec.{h,cpp}: the frame <-> SurfaceOp/SurfaceReply
codec, the explicit WindowBackend <-> WindowKind table, and the wire
entry ServerApplyWireSurfaceOp whose refusals are named:
Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"} and
Fatal{ProtocolCorruption, "SurfaceOp"}.
- ServerLoop: RunOnApplyThread(fn, void*) -> RunSurfaceControlFrame(frame)
plus the dispatch ApplySurfaceControlFrame; the old per-forwarder Args
bodies moved into the dispatch unchanged (C7/ID-54, N-3, R-12 included).
RunProbeOnApplyThreadForTesting is the test seam through the same
channel (the hook lives in a member, never in the slot).
- Tests: RemoteClientTest/ServerLoopTest migrated to the probe seam; new
SurfaceControlFrameTest suite (mapping tables, wire round-trips, the
two named refusals) plus ServerLoopTest's AVoidForwarderCrossesAsOne-
DispatchedFrame (the frame channel's red-once handle) and
ServerLoopEglTest's TheInitializeDisplayReplyArrivesThroughTheFrame.
6.6 KiB
6.6 KiB
fc — 控制面帧契约草稿(P5f 包 fc;CONTRACT-P5F.md 本体留给集成收口)
Status: DRAFT, landed in code by package
fcon branchp5f/fc. Authority once integrated: this feeds CONTRACT-P5F.md. Until then it supplements CONTRACT-P5C (audit row G4, discharged here) and CONTRACT-P5E §2.5 (the wait-before-forwarder discipline, unchanged), and it answers the control-plane rows ofdocs/Disaggregated/P6-CONTRACT-DRAFT.md§4 ahead of schedule. Code anchors:MobileGL/MG_Remote/Server/SurfaceControlFrame.h(the frame),MobileGL/MG_Remote/Protocol/SurfaceOpCodec.{h,cpp}(the wire codec and the named refusals),MobileGL/MG_Remote/Server/ServerLoop.cpp(RunSurfaceControlFrame/ApplySurfaceControlFrame).
1. Ownership
- A SurfaceControlFrame is a value. The poster fills the request fields; ownership of the
slot's content passes to the channel at publish; the dispatch on the apply thread fills the
reply half (
ok,eglMajor,eglMinor) into the same value; the poster's copy-out happens after the blocking handshake returns. No field is a pointer and none may become one — thestatic_assert(std::is_trivially_copyable_v<...>)inSurfaceControlFrame.his the enforcement, not a courtesy. seqis minted by the channel's poster side (RunSurfaceControlFrame), monotonically from 1 per session, and echoed in the reply. Under spawn the client mints (P6-CONTRACT-DRAFT table 0); the field does not change shape.display/surface/readSurface/contextare Uint64 tokens. Inproc they are the client's EGL handle values bit-cast — legal only because both roles share a process and a driver, and the N-3 tuple bookkeeping compares them by value. Under spawn they are client-minted dense tokens (P6-CONTRACT-DRAFT table 0, unchanged). No code may dereference a token as a pointer on a path a non-inproc transport can reach.windowBackend+nativeToken+width/heightcarry aWindowHandleas values. The frame names the backend enum, never the wire'sWindowKind; the two enums meet only insideSurfaceOpCodec.cpp's explicit tables (Surfaceless/Pbufferare surface shapes and answer "no backend" there). AnativeTokenwhose backend is Android is anANativeWindow*— an address in the client's process — and may never cross (§3.4).
2. Ordering
- The channel keeps its P5 semantics: one blocking slot,
m_callerMutexserialising posters, pumped by the apply thread between drain batches, publish-then-ring. Frames are FIFO against each other; they are NOT ordered against SEG_CMD records by the channel itself. - The client-side discipline that orders frames against records is unchanged and remains
mandatory under spawn:
WaitForApplyBeforeEglForwarderwaitsappliedSeqtoLastPublishedSeqbefore every forwarder (CONTRACT-P5E §2.5), because the channel is pumped between drain batches and a control frame would otherwise overtake in-flight records. - All twelve ops stay blocking in fc, including the three f0-egl §4.3 marked relaxable
(
SetSwapInterval,ReleaseSurface,SetWindowHandle). Relaxing them to fire-and-forget is a P6 decision and must preserve: aSetWindowHandleis in effect before any laterCreateWindowSurface(one ordered channel suffices), andReleaseResourcesNEVER relaxes —MobileGL::Destroy()walks on the moment it answers (ServerLoop.h's header note). - Re-entrancy is unchanged: a post from the apply thread dispatches inline (the
~BackendObject_DirectGLES→ReleaseEGLResourcespath). This is thread semantics, not wire semantics; under spawn the destructor path runs server-side natively and never posts.
3. Error semantics
- Loop down, backend alive →
Fatal{ApplyThreadNotRunning}(M-7, unchanged). - Loop down, no backend →
MOBILEGL_ERR_NOT_INITIALIZED, nothing runs on the caller (C2, unchanged). - Backend null at dispatch →
MOBILEGL_ERR_NOT_INITIALIZEDwithok=false; the forwarder's answer is the oldfalse/void-drop, unchanged. - Wire entry (
ServerApplyWireSurfaceOp, the future P6 control pump's entry point; P5f drives it from tests only): a decode failure isFatal{ProtocolCorruption, "SurfaceOp"}, never a dropped frame (a dropped control frame is a client waiting on a reply that never comes); a decodedWindowKind::AndroidNativeWindowisFatal{UnmigratedSurface, "AndroidNativeWindow@P12"}— the window kind is legal schema, the token is the part that means nothing in the server's process, and real window arrival is P12. Inproc-only kinds (InitCapabilitiesInprocOnly,SwapBuffersInprocOnly,InitWindowSurfaceInprocOnly,ProbeForTesting) cannot arrive: the encoder refuses them (SurfaceWireError::InprocOnlyOpOnTheWire). - A frame that reaches the inproc dispatch with
kind == Noneor a kind the switch does not know isFatal{ProtocolCorruption, "SurfaceOp.kind"}; awindowBackendtag outside the enum isFatal{ProtocolCorruption, "SurfaceOp.windowBackend"}. Inproc these are corruption shapes, not bad input — same discipline as the ring's header check.
4. Reply carriers — and the deliberate non-use of SurfaceReply.defaultFb
- The default framebuffer's shape already crosses as
kEventSurfaceChangedon SEG_EVENT (P5c ev), and the client drains it at the RPC return (BackendObject_Remote.cpp's create/resize arms). fc rules thatSurfaceReply.defaultFbstays unset: two spellings of one fact is how the two sides come to disagree about it (theCapsSnapshotdeprecated-fields lesson,protocol.fbs). Enabling it later is a schema amendment, not a silent flip. - The caps republish side effects are unchanged and are server-spontaneous frames on the wire:
a real bind under
MakeCurrentrepublishes (R-12 arm (a));InitCapabilitiespublishes. The client's absorption point — pump the control plane after the RPC returns — is the same in both worlds: "after this op's reply, before the next op" is a total order the client can rely on.
5. What fc deliberately did NOT do
- No transport: no socket, no spawn pump, no client-minted token table (the frame fields are token-width already, so P6 is transport-only here).
- The two dead forwarders (
ServerSwapEGLBuffers,ServerInitWindowSurface) andServerInitCapabilitieswere NOT given wire ops (f0-egl F8/§4.2): they ride the inproc channel as inproc-only kinds because the function-pointer mailbox is gone entirely, and G14 forbids deleting their pinning tests. - Blocking semantics, the shadow, the doorbell order and the C2 exit block: unchanged.