mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
105 lines
7.4 KiB
Markdown
105 lines
7.4 KiB
Markdown
# fc — 控制面帧契约草稿(P5f 包 fc;CONTRACT-P5F.md 本体留给集成收口)
|
||
|
||
> Status: DRAFT, landed in code by package `fc` on branch `p5f/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 of `docs/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
|
||
|
||
1. 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 in `SurfaceControlFrame.h` checks 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.
|
||
2. An unnumbered local request (`seq == 0`) is numbered by `RunSurfaceControlFrame`, 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.
|
||
3. `display` / `surface` / `readSurface` / `context` are 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.
|
||
4. `windowBackend` + `nativeToken` + `width`/`height` carry a `WindowHandle` as values. The frame
|
||
names the **backend** enum, never the wire's `WindowKind`; the two enums meet only inside
|
||
`SurfaceOpCodec.cpp`'s explicit tables (`Surfaceless`/`Pbuffer` are surface shapes and answer
|
||
"no backend" there). A `nativeToken` whose backend is Android is an `ANativeWindow*` — an
|
||
address in the client's process — and may never cross (§3.5). MetalLayer carries a
|
||
`CAMetalLayer*` and has the same restriction.
|
||
|
||
## 2. Ordering
|
||
|
||
1. The channel keeps its P5 semantics: one blocking slot, `m_callerMutex` serialising 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.
|
||
2. The client-side discipline that orders frames against records is unchanged and remains
|
||
mandatory under spawn: `WaitForApplyBeforeEglForwarder` waits `appliedSeq` to
|
||
`LastPublishedSeq` before every forwarder (CONTRACT-P5E §2.5), because the channel is pumped
|
||
between drain batches and a control frame would otherwise overtake in-flight records.
|
||
3. **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: a `SetWindowHandle` is in effect before any later
|
||
`CreateWindowSurface` (one ordered channel suffices), and `ReleaseResources` NEVER relaxes —
|
||
`MobileGL::Destroy()` walks on the moment it answers (ServerLoop.h's header note).
|
||
4. Re-entrancy is unchanged: a post from the apply thread dispatches inline (the
|
||
`~BackendObject_DirectGLES` → `ReleaseEGLResources` path). This is thread semantics, not wire
|
||
semantics; under spawn the destructor path runs server-side natively and never posts.
|
||
|
||
## 3. Error semantics
|
||
|
||
1. Loop down, backend alive → `Fatal{ApplyThreadNotRunning}` (M-7, unchanged).
|
||
2. Loop down, no backend → `MOBILEGL_ERR_NOT_INITIALIZED`, nothing runs on the caller (C2,
|
||
unchanged).
|
||
3. Backend null at dispatch → `MOBILEGL_ERR_NOT_INITIALIZED` with `ok=false`; the forwarder's
|
||
answer is the old `false`/void-drop, unchanged.
|
||
4. Successful void operations (`SetSwapInterval`, `ReleaseSurface`, `ReleaseResources`,
|
||
`SetWindowHandle`) reply `ok=true` after completing on the apply thread; a null backend
|
||
replies `ok=false`. The wire therefore preserves the success/failure distinction even
|
||
though the inproc forwarder returns void.
|
||
5. Wire entry (`ServerApplyWireSurfaceOp`, the future P6 control pump's entry point; P5f drives
|
||
it from tests only): a decode failure is **`Fatal{ProtocolCorruption, "SurfaceOp"}`**, never a
|
||
dropped frame (a dropped control frame is a client waiting on a reply that never comes); a
|
||
decoded `WindowKind::AndroidNativeWindow` is
|
||
**`Fatal{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 decoded `MetalLayer` is similarly **`Fatal{UnmigratedSurface, "MetalLayer@P12"}`**,
|
||
because its `CAMetalLayer*` also belongs to the client process. Inproc-only kinds
|
||
(`InitCapabilitiesInprocOnly`, `SwapBuffersInprocOnly`,
|
||
`InitWindowSurfaceInprocOnly`, `ProbeForTesting`) cannot arrive: the encoder refuses them
|
||
(`SurfaceWireError::InprocOnlyOpOnTheWire`).
|
||
6. A frame that reaches the inproc dispatch with `kind == None` or a kind the switch does not know
|
||
is `Fatal{ProtocolCorruption, "SurfaceOp.kind"}`; a `windowBackend` tag outside the enum is
|
||
`Fatal{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`
|
||
|
||
1. The default framebuffer's shape already crosses as `kEventSurfaceChanged` on SEG_EVENT (P5c
|
||
ev), and the client drains it at the RPC return (`BackendObject_Remote.cpp`'s create/resize
|
||
arms). fc rules that `SurfaceReply.defaultFb` **stays unset**: two spellings of one fact is how
|
||
the two sides come to disagree about it (the `CapsSnapshot` deprecated-fields lesson,
|
||
`protocol.fbs`). Enabling it later is a schema amendment, not a silent flip.
|
||
2. The caps republish side effects are unchanged and are server-spontaneous frames on the wire:
|
||
a real bind under `MakeCurrent` republishes (R-12 arm (a)); `InitCapabilities` publishes. 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`) and
|
||
`ServerInitCapabilities` were 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.
|