mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Breaks the one process = one server = one session = one context equation
that the whole stack's correctness used to rest on. Design and per-slice
shape: docs/Disaggregated/design/11-state-ownership.md.
S1 wire: bind_context ring record (opcode 84) announces the current
context at the make-current edge; CreateContext/DestroyContext join the
SurfaceOp control frames; control revision 5 -> 6; the server keeps a
per-session token table and refuses unknown tokens by name.
S2 server: SessionRuntime per session with thread-scoped resolution
(singletons become fallbacks), per-thread gPipeInputs, per-session latch
domains, per-thread segment resolvers; the in-process unix supervisor
serves up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (16) concurrent sessions;
surface mode is declared per session and the display lease refuses a
second on-screen session by name.
S3 frontend: ShareGroupState owns the shared object stores and name
generators (buffer/texture/renderbuffer/sampler/program), GLContext keeps
per-context bindings/errors/container objects; pGLContext becomes
thread-local with a process default context preserving the single-context
world verbatim; eglCreateContext builds a real GLContext and joins the
sharing context's group.
S4 backend: native EGL tuples keyed by (session, context token);
InitDisplayAndContext's unconditional teardown is gone - creating a
second context or surface no longer destroys the first; eglCreateContext
sharing is wired to the native driver; bind_context is the native switch
point.
S5 applier: MGPipeApplierState splits into MGPipeObjectRecords (per
share group) and MGPipeWorkingState (per context), resolved through a
{session, token} registry with a thread-local cached fast path.
Tests: MultiSessionTest (incl. two sessions x two contexts interleaved
with real clear/readback witnesses), ApplierOwnershipTest (with a
null-resolver negative control), ContextStateOwnershipTest,
ServerLoopEglTest native-context liveness; 99/99 of the acceptance set
green on the host build.
Known debts (documented in the design doc): twin registries still keyed
by handle alone, no per-record ContextSlot, process-global backend
generations, MakeCurrent dedup per current binding.
460 lines
19 KiB
Plaintext
460 lines
19 KiB
Plaintext
// MobileGL - MobileGL/MG_Remote/Protocol/protocol.fbs
|
|
// 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
|
|
|
|
// MobileGL disaggregated wire protocol - CONTROL PLANE ONLY.
|
|
//
|
|
// Plan B (docs plan "MGPipe") section 8.1 inherits the transport design of the
|
|
// earlier plan verbatim, and its section 7.1 splits the schema in two:
|
|
//
|
|
// - rare / variable-length / must-evolve messages -> FlatBuffers *tables*,
|
|
// carried as complete framed messages over the control channel. That is
|
|
// everything in this file.
|
|
// - the hot path -> FlatBuffers *structs* (fixed layout, no vtable, no
|
|
// offset indirection) written straight into the SEG_CMD ring. Those
|
|
// records are generated from MG_Pipe/PipeCalls.def and are deliberately
|
|
// NOT in this schema yet: the call catalogue is a separate P0 deliverable
|
|
// and record numbering must never churn.
|
|
//
|
|
// Regeneration: scripts/gen_protocol.py (flatc is NOT part of the default
|
|
// build graph). generated/protocol_generated.h is committed and CI's
|
|
// flatc-check regenerates it and runs `git diff --exit-code`.
|
|
|
|
namespace MobileGL.Wire;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Segments
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Segment layout is inherited unchanged (earlier plan section 6.1):
|
|
// SEG_CMD 8MiB / SEG_STAGE 32MiB+ / SEG_REPLY 8MiB / SEG_EVENT 256KiB /
|
|
// SEG_SHADOW[n] / SEG_ADOPT[n].
|
|
enum SegmentKind : ubyte {
|
|
None = 0,
|
|
Cmd = 1, // client-owned command ring (RingControl + records)
|
|
Stage = 2, // client-owned bulk staging
|
|
Reply = 3, // server-owned reply pool
|
|
Event = 4, // server-owned event ring
|
|
Shadow = 5, // client-owned per-object shadow (P4.5+)
|
|
Adopt = 6, // server-owned adopted store, client RW (>= 16MiB)
|
|
}
|
|
|
|
// The fd itself never travels in a message: POSIX passes it with SCM_RIGHTS on
|
|
// the aux socket (ITransport::ShareFd), Windows resolves `name`.
|
|
table SegmentRef {
|
|
id: uint;
|
|
kind: SegmentKind;
|
|
sizeBytes: ulong;
|
|
name: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Handshake
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// P6.5: independent control and data-plane choices. Values are append-only.
|
|
enum DataPlane : ubyte { SharedSegments = 0, Stream = 1 }
|
|
enum WireForm : ubyte { StructImage = 0 }
|
|
enum DialMode : ubyte { No = 0, Fork = 1, Connect = 2 }
|
|
|
|
// Window sizes are stated by the server; the client's values are proposals only.
|
|
table LinkTerms {
|
|
dataPlane: DataPlane;
|
|
wireForm: WireForm;
|
|
maxReplyBytes: ulong;
|
|
cmdWindowBytes: ulong;
|
|
stageWindowBytes: ulong;
|
|
eventWindowBytes: ulong;
|
|
// P11 B2 (MG_Remote/CONTRACT-P11.md B2), appended. In the Hello: the persistent-map adoption
|
|
// tier the client ASKS for - 0 = T0 (it allocates each adopted store as an AHardwareBuffer and
|
|
// hands it over the aux socket), 0x80 = T0 by DEFAULT (the client's knob is unset: the same T0,
|
|
// but the server names a refusal only at debug level), 2 = T2 (emulate: shadow + push). 1 (T1)
|
|
// is closed and never sent; a stream plane always asks 2. In the Welcome: the server's echo of the ask it
|
|
// recorded. It is not the tier: T0 needs the server's POST self-test, which runs at the first
|
|
// native bind (the GPU context does not exist yet), and is published as kCapAdoptT0 in the
|
|
// CapsSnapshot that bind republishes. Absent (an older peer) reads 2.
|
|
adoptTier: ubyte = 2;
|
|
}
|
|
|
|
enum RefuseCode : uint {
|
|
None = 0,
|
|
ProtocolVersion = 1,
|
|
WireFingerprint = 2,
|
|
BuildFingerprint = 3,
|
|
Authentication = 4,
|
|
LinkTerms = 5,
|
|
Backend = 6,
|
|
Busy = 7,
|
|
MalformedHello = 8,
|
|
}
|
|
|
|
// A handshake rejection is a normal session outcome, never a process abort.
|
|
table Refuse {
|
|
code: RefuseCode;
|
|
detail: string;
|
|
expected: ulong;
|
|
actual: ulong;
|
|
peerValue: string;
|
|
}
|
|
|
|
table LogFlush {
|
|
seq: ulong;
|
|
ack: bool;
|
|
}
|
|
|
|
table Hello {
|
|
abiMajor: uint;
|
|
abiMinor: uint;
|
|
buildFingerprint: string;
|
|
backendType: uint;
|
|
pid: uint;
|
|
configBlob: [ubyte];
|
|
// Retained legacy slot. P6.5 peers compare wireFingerprint instead.
|
|
abiFingerprint: ulong;
|
|
wireFingerprint: ulong;
|
|
linkTerms: LinkTerms;
|
|
token: string;
|
|
dialMode: DialMode;
|
|
}
|
|
|
|
table Welcome {
|
|
abiMajor: uint;
|
|
abiMinor: uint;
|
|
serverPid: uint;
|
|
cmdRing: SegmentRef;
|
|
stageRing: SegmentRef;
|
|
replyPool: SegmentRef;
|
|
eventRing: SegmentRef;
|
|
buildFingerprint: string;
|
|
// Retained legacy slot; do not reuse or renumber it.
|
|
abiFingerprint: ulong;
|
|
wireFingerprint: ulong;
|
|
linkTerms: LinkTerms;
|
|
backendType: uint;
|
|
// PH-7 (4), ID-P7-3. 128 bits from the server's CSPRNG, minted after the Hello authenticated
|
|
// and only on a Stream data plane. The client opens its data connection AFTER this Welcome
|
|
// and presents the value as that connection's first frame (DataBind); the server binds the
|
|
// connection to this session by the value, not by which connection arrived second. Absent on
|
|
// SharedSegments, whose descriptors travel over the control connection's own aux socket.
|
|
dataNonce: [ubyte];
|
|
}
|
|
|
|
// PH-7 (4). The FIRST and only control-framed message on a TCP data connection: the Welcome's
|
|
// dataNonce, handed back. Everything after it on that connection is StreamLink's own framing.
|
|
// A connection whose first frame is not this, or whose nonce is not the live session's, is
|
|
// refused by name (`Refuse{Authentication}`) and never becomes a data plane.
|
|
table DataBind {
|
|
nonce: [ubyte];
|
|
}
|
|
|
|
// P11 PAIR. The FIRST frame on each of the two connections a socket client opens with
|
|
// SocketTransport::ConnectTo - control, then aux (the SCM_RIGHTS socket; the data connection on a
|
|
// tcp:// ConnectTo) - and on control the only frame before the Hello. `nonce` is 128 bits from the
|
|
// client's CSPRNG, the same on both; `aux` says which of the two this connection is. The server
|
|
// holds every accepted connection until it has presented this and pairs the two halves by the
|
|
// nonce, never by arrival order (Server/PairAcceptor.h): a stray connection, or a second client
|
|
// connecting at the same moment, cannot become half of this client's session. A control connection
|
|
// whose first frame is a Hello is a peer from before this message and is refused by name.
|
|
table PairBind {
|
|
nonce: [ubyte];
|
|
aux: bool;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Capabilities
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Replaces the 40 `pActiveBackendObject->` reads plus the 89 caps read sites
|
|
// (plan B appendix A, `get_caps`). The three blobs are byte-for-byte images of
|
|
// the corresponding POD structs; they are versioned by structSize-first
|
|
// discipline, not by this schema.
|
|
// The four fields this table used to end with are RETIRED (CONTRACT-P5 table 0,
|
|
// "CapsSnapshot redundancy"):
|
|
//
|
|
// maxComputeWorkGroupCount / maxComputeWorkGroupSize - they ride inside
|
|
// `dynamicParameters` already (BackendObject.h:392-393), and two spellings of
|
|
// one number is how the two sides come to disagree about it.
|
|
// tableSlotMask - GLFunctionsTable has SIXTY-NINE function-pointer slots
|
|
// (BackendObject.h:117-292) and a ulong is 64 bits, so the field could never
|
|
// address the table its own comment named; and ARCHITECTURE.md:114 already
|
|
// retired "is this table slot null" as the capability probe in favour of
|
|
// CallMask, so keeping it would re-introduce exactly what replaced it.
|
|
// prefersCpuXfbPrimitiveAccounting - answered by kCapCpuXfbPrimitiveAccounting
|
|
// in `callMask` below.
|
|
//
|
|
// THEY ARE `(deprecated)`, NOT DELETED, AND THAT IS NOT A STYLE CHOICE. In
|
|
// FlatBuffers a table field's id IS its vtable slot, and REMOVING a field FREES
|
|
// that slot for the next field appended to the table - so plainly deleting these
|
|
// four would have handed slots 14 and 16, which used to carry `[int]` vectors
|
|
// (4-byte uoffsets), to `callMask` (an 8-byte inline ulong) and `backendType` (a
|
|
// 4-byte inline uint). Two peers straddling that edit both still announce
|
|
// abiMajor 1, and the ABI fingerprint mixes struct sizes and a git stamp, not the
|
|
// schema, so neither the handshake nor the fingerprint could see it: the reader
|
|
// would parse a uoffset as a ulong. `(deprecated)` keeps 14/18/20 burned, pushes
|
|
// the two new fields to 22/24, generates no accessor for the retired names so
|
|
// nothing can read or write them, and costs zero bytes on the wire. The
|
|
// alternative - bumping MOBILEGL_PROTOCOL_ABI_MAJOR - is a real break for a
|
|
// change that does not need to be one.
|
|
table CapsSnapshot {
|
|
dynamicParameters: [ubyte];
|
|
rendererInfo: [ubyte];
|
|
formatCaps: [ubyte];
|
|
extensions: [string];
|
|
apiVersion: string;
|
|
maxComputeWorkGroupCount: [int] (deprecated);
|
|
maxComputeWorkGroupSize: [int] (deprecated);
|
|
tableSlotMask: ulong (deprecated);
|
|
prefersCpuXfbPrimitiveAccounting: bool (deprecated);
|
|
// MGPCaps::CallMask (MGPipeTypes.h:127). Bits 0..8 are MGPCapBit; bits 32..47
|
|
// are the CONSUMER MASK - bit (32+n) means "the server has a consumer for
|
|
// MGPipe subsystem bit n" - and MG_Remote/CapsCodec.h holds the four constexprs
|
|
// that are the only legal way to fold and test them. It needs a carrier of its
|
|
// own because `dynamicParameters` is the image of DynamicBackendParameters,
|
|
// which CallMask is not a member of; and without a carrier R-8's rule that the
|
|
// client's liveness gates read the caps mirror has nothing to read.
|
|
callMask: ulong;
|
|
// The SERVER's backend type, for CapsMirror::Backend(). Hello.backendType is
|
|
// the CLIENT's request; this is the answer, and the frontend branches that
|
|
// switch on it (GL_Framebuffer.cpp:47, GL_Texture.cpp:6536, CompileEnv.cpp:122)
|
|
// take a wrong arm rather than fail on a value they do not know, so it may not
|
|
// be guessed from the renderer string.
|
|
backendType: uint;
|
|
}
|
|
|
|
table DefaultFramebufferInfo {
|
|
width: int;
|
|
height: int;
|
|
colorFormat: uint;
|
|
depthFormat: uint;
|
|
stencilFormat: uint;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Surface / EGL lifecycle
|
|
// ---------------------------------------------------------------------------
|
|
|
|
enum SurfaceOpKind : ubyte {
|
|
None = 0,
|
|
InitializeDisplay = 1,
|
|
CreateWindowSurface = 2,
|
|
CreatePbufferSurface = 3,
|
|
ResizeWindowSurface = 4,
|
|
ReleaseSurface = 5,
|
|
MakeCurrent = 6,
|
|
ReleaseCurrent = 7,
|
|
// P5f (package fc) appended these three. Append-only, always: the enum's values ARE the wire.
|
|
SetSwapInterval = 8,
|
|
ReleaseResources = 9,
|
|
SetWindowHandle = 10,
|
|
// P6 (package cp) appended this one. The draft held that InitCapabilities needs
|
|
// no wire form because "the CapsSnapshot answers it" - but a CapsSnapshot is the
|
|
// ANSWER and this is the REQUEST, and a spawn client has a live caller for it
|
|
// (Client/BackendObject_Remote.cpp). Without the tag the codec refused it by
|
|
// name and every spawned eglMakeCurrent failed with InprocOnlyOpOnTheWire.
|
|
InitCapabilities = 11,
|
|
// P14 S1 (docs/Disaggregated/design/11-state-ownership.md). THE EGL CONTEXT LIFECYCLE, as two
|
|
// more ops on the same frame the nine surface ops ride - the shape §"wire changes" 2 prescribes,
|
|
// and deliberately NOT two new CtrlMsg tables: a context is created, destroyed and made current
|
|
// by the SAME caller on the SAME thread as a surface, through the same blocking frame channel,
|
|
// and a second channel would only be a second place for the two orderings to disagree.
|
|
//
|
|
// CreateContext `context` is the clientContextToken EGLState minted, `shareGroupToken` the
|
|
// group it was derived into (the sharing context's group, or a fresh one),
|
|
// `contextFlags` the frame's reserved extension word (0 today). `display` and
|
|
// `surface` are 0: a context is display-scoped in EGL but the server's display
|
|
// is the session's own, so nothing is named.
|
|
// DestroyContext `context` is the token to drop. The server forgets it and, if it was the
|
|
// bound one, unbinds it.
|
|
//
|
|
// Both are answered by the ordinary SurfaceReply (ok, plus the refusal enum) - S1 creates a
|
|
// PLACEHOLDER runtime (token, share group, flags) and no native context, so there is nothing
|
|
// for the reply to carry that a Bool does not say already.
|
|
CreateContext = 12,
|
|
DestroyContext = 13,
|
|
}
|
|
|
|
enum WindowKind : ubyte {
|
|
None = 0,
|
|
AndroidNativeWindow = 1,
|
|
X11 = 2,
|
|
Win32Hwnd = 3,
|
|
Surfaceless = 4,
|
|
Pbuffer = 5,
|
|
// P5f (package fc): WindowBackend (MG_Backend/BackendObject.h) has a fourth window backend and
|
|
// the two enums may only meet through the explicit mapping table in Protocol/SurfaceOpCodec.cpp
|
|
// - Surfaceless/Pbuffer are surface SHAPES, not window backends, and answer "no backend" there.
|
|
MetalLayer = 6,
|
|
// P12 (on-screen server window), appended. THE WINDOW IS THE SERVER'S OWN: the client asks the
|
|
// server to create the surface on the window the server process owns (the display Activity's
|
|
// SurfaceView) and has none of its own to name, so `nativeToken` is always 0 and no pointer
|
|
// crosses (Rule G/H). Legal on CreateWindowSurface only; SetWindowHandle may not name it.
|
|
ServerOwned = 7,
|
|
}
|
|
|
|
// P12, appended: WHY A SURFACE OP WAS DECLINED, so the client can name the refusal in its own log
|
|
// instead of reading a bare `ok = false`. None of these latches the session: each is a
|
|
// configuration or ordering answer, not a peer's corrupt bytes.
|
|
enum SurfaceRefusal : ubyte {
|
|
None = 0,
|
|
NoServerDisplay = 1, // ServerOwned asked of a server that owns no display
|
|
NoServerWindow = 2, // the server owns a display, but no window came within its wait
|
|
SurfaceModeMismatch = 3, // the session's surface mode (on-screen / offscreen) is the other one
|
|
ServerOwnedOnSetWindowHandle = 4, // SetWindowHandle named the server's own window
|
|
}
|
|
|
|
table SurfaceOp {
|
|
seq: ulong;
|
|
kind: SurfaceOpKind;
|
|
display: ulong;
|
|
surface: ulong;
|
|
windowKind: WindowKind;
|
|
nativeToken: ulong; // X11 XID / HWND; 0 for ServerOwned (the server substitutes its own window)
|
|
width: int;
|
|
height: int;
|
|
swapInterval: int;
|
|
// P5f (package fc), appended (a FlatBuffers table field's id IS its vtable slot - append only):
|
|
// MakeCurrent's semantic unit is the (display, draw, read, context) four-tuple, and `surface`
|
|
// alone cannot carry it. `surface` stays the DRAW surface; `readSurface` and `context` are 0
|
|
// ("no surface" / "no context") for every other op and for a ReleaseCurrent.
|
|
readSurface: ulong;
|
|
context: ulong;
|
|
// P14 S1, appended (a FlatBuffers table field's id IS its vtable slot - append only). The two
|
|
// values CreateContext carries beyond the token itself: the share group the new context was
|
|
// derived into, and a reserved flags word so a later context attribute (a profile, a robustness
|
|
// ask) has a slot to land in without moving the fields beside it. 0 on every other op.
|
|
shareGroupToken: ulong;
|
|
contextFlags: uint;
|
|
}
|
|
|
|
table SurfaceReply {
|
|
seq: ulong;
|
|
ok: bool;
|
|
eglMajor: int;
|
|
eglMinor: int;
|
|
defaultFb: DefaultFramebufferInfo;
|
|
// P6.5: all preceding data-plane events must arrive before this reply is observed.
|
|
eventHead: ulong;
|
|
// P12, appended. The surface's REAL extent as the server has it - for a ServerOwned
|
|
// CreateWindowSurface, the server window's size - so a headless client knows its surface
|
|
// geometry when eglCreateWindowSurface returns, before any backend has published a
|
|
// surface-changed event (Magma creates its swapchain only at the first MakeCurrent). 0/0 for
|
|
// every other op.
|
|
width: int;
|
|
height: int;
|
|
refusal: SurfaceRefusal;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Resync / aux / diagnostics
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Sent by the client after it observes a serverEpoch bump (context lost or
|
|
// server restart): every cached ring offset and every server-side object is
|
|
// gone and the whole pushed state has to be replayed.
|
|
table ResyncRequest {
|
|
serverEpoch: uint;
|
|
}
|
|
|
|
table ResyncDone {}
|
|
|
|
enum AuxRequestKind : ubyte {
|
|
None = 0,
|
|
FenceClientWait = 1,
|
|
QueryResult = 2,
|
|
ScalarGet = 3,
|
|
}
|
|
|
|
// Requests issued from a thread that is not the ring producer (foreign-thread
|
|
// sync / query polling), so they cannot take the SPSC ring.
|
|
table AuxRequest {
|
|
seq: ulong;
|
|
kind: AuxRequestKind;
|
|
payload: [ubyte];
|
|
}
|
|
|
|
enum FatalCode : uint {
|
|
None = 0,
|
|
ProtocolCorruption = 1, // record bounds / self-describing length violated
|
|
RingOverrun = 2,
|
|
SegmentMismatch = 3,
|
|
DeviceLost = 4,
|
|
ServerCrashed = 5,
|
|
AbiMismatch = 6,
|
|
}
|
|
|
|
table Fatal {
|
|
code: FatalCode;
|
|
message: string;
|
|
// P6 dl (CONTRACT-P6 5.2, D2). The internal MGFatalFamily word, so a peer that receives this
|
|
// frame before the sender aborts can name WHICH family ended the session instead of reading a
|
|
// bare EOF. `code` stays the coarse seven-value projection; this carries the full vocabulary.
|
|
// Append-only: an older peer that does not read it still gets `code` and `message`.
|
|
family: string;
|
|
}
|
|
|
|
// Severity-graded per plan B section 8.2: <= Warn is lossy, >= Error is
|
|
// lossless and rate limited.
|
|
enum LogLevel : ubyte {
|
|
Debug = 0,
|
|
Info = 1,
|
|
Warn = 2,
|
|
Error = 3,
|
|
Fatal = 4,
|
|
}
|
|
|
|
table LogLine {
|
|
level: LogLevel;
|
|
text: string;
|
|
}
|
|
|
|
// P7 (p7/spawnhang). THE SERVER IS STILL RUNNING THE CLIENT'S SURFACE OP. Sent only by the
|
|
// server's control pump, only while its apply thread is running a SurfaceOp it has TAKEN, and
|
|
// only before that op's SurfaceReply - once per progress interval after the op has run that long
|
|
// (ServerLoop.cpp, kControlProgressIntervalMs). It is how a client tells a server that is BUSY
|
|
// with its op (a cold native bring-up inside the first surface creation or MakeCurrent: eglInitialize
|
|
// loading a software rasteriser off a cold disk) from one that is SILENT: the client's reply budget
|
|
// restarts on each report, up to a ceiling. A server that never took the op, or is frozen, sends
|
|
// none and still times out by name (CONTRACT-P6 5.4 D5b).
|
|
table SurfaceProgress {
|
|
seq: ulong; // the SurfaceOp.seq being run
|
|
elapsedMs: uint; // since the op was posted to the apply thread
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Envelope
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Union tags are wire values: only ever APPEND to this list.
|
|
// ProgramReflection from the earlier plan's section 7.1 is intentionally
|
|
// absent - plan B ships program artifacts inside the create_shader_state CSO
|
|
// blob, so if a control-plane reflection message is ever needed it appends
|
|
// here rather than reserving a tag today.
|
|
union CtrlMsg {
|
|
Hello,
|
|
Welcome,
|
|
CapsSnapshot,
|
|
SurfaceOp,
|
|
SurfaceReply,
|
|
ResyncRequest,
|
|
ResyncDone,
|
|
AuxRequest,
|
|
Fatal,
|
|
LogLine,
|
|
Refuse,
|
|
LogFlush,
|
|
DataBind,
|
|
SurfaceProgress,
|
|
PairBind,
|
|
}
|
|
|
|
table CtrlEnvelope {
|
|
msg: CtrlMsg;
|
|
}
|
|
|
|
root_type CtrlEnvelope;
|
|
file_identifier "MGLC";
|