mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-12 20:43: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.
96 lines
6.6 KiB
Markdown
96 lines
6.6 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
|
||
`static_assert(std::is_trivially_copyable_v<...>)` in `SurfaceControlFrame.h` is the
|
||
enforcement, not a courtesy.
|
||
2. `seq` is 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.
|
||
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.4).
|
||
|
||
## 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. 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. Inproc-only kinds (`InitCapabilitiesInprocOnly`, `SwapBuffersInprocOnly`,
|
||
`InitWindowSurfaceInprocOnly`, `ProbeForTesting`) cannot arrive: the encoder refuses them
|
||
(`SurfaceWireError::InprocOnlyOpOnTheWire`).
|
||
5. 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.
|