mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
7.4 KiB
7.4 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 — the exhaustive constexpr structured binding inSurfaceControlFrame.hchecks every member as an integer or enum. Adding a member without extending the binding fails compilation; changing a checked member to a pointer fails its static assertion. Trivially-copyable / standard-layout remain separate copyability checks: neither trait rejects pointers. - An unnumbered local request (
seq == 0) is numbered byRunSurfaceControlFrame, from 1 per session. An already numbered wire request retains its client-minted sequence through dispatch and reply (P6-CONTRACT-DRAFT table 0); the server never renumbers it. 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.5). MetalLayer carries aCAMetalLayer*and has the same restriction.
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. - Successful void operations (
SetSwapInterval,ReleaseSurface,ReleaseResources,SetWindowHandle) replyok=trueafter completing on the apply thread; a null backend repliesok=false. The wire therefore preserves the success/failure distinction even though the inproc forwarder returns void. - 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. A decodedMetalLayeris similarlyFatal{UnmigratedSurface, "MetalLayer@P12"}, because itsCAMetalLayer*also belongs to the client process. 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.