Skip to content

yclass codegen: locality is per class, not per slot — retire local@, add class-level rpc@ (local by default) #707

Description

@zokrezyl

Part of epic #712.

Problem

local@<DOMAIN>:<SLOT> ("generate no RPC boilerplate for this slot") is per-slot only. A survey of every use shows the annotation is modeling the wrong concept at the wrong granularity.

Usage today:

class local slots
api_yplot 13/13
yview 9/9
ynet capture 14/14
ymap 16/16
ymusic 6/6
yflame 16/16
ycircuit 12/12
ychrome 6/6
yjupyter (client + protocol) 27/27
ynotebook 33/33
yapp 3/3
yfigure base slots 9/9
ytermsink sink 5/5
yplatform (window, platform, clipboard, window-chrome) ~25/25
yscene 1/15 (only set_registry)
ygui widget 3/9 (widget_paint, widget_emit_container, widget_emit_body)
yrich document 3/14 (render, apply_op, destroy)

~190 annotation lines restating a per-class fact slot by slot, plus a prose comment per file saying the same thing ("Every slot is local@ — never proxied over RPC").

The conceptual bug

Locality in yclass is already a property of the object reference, resolved at runtime: the generated stub dispatches directly when ctx->session == NULL and marshals over the wire when a session is set. An object is local or remote; its methods are not individually one or the other.

What a per-slot local@ actually produces on a proxy is a method whose stub has no RPC branch — calling it dispatches "locally" into an object with no implementation on this side. It is not "a method that runs locally"; it is a method absent from the remote interface. local@ is therefore encoding interface membership as a per-method attribute — a workaround for the model having no way to say "this class is not remotable" / "this base class is host-side."

The three "mixed" classes are each two interfaces fused into one class:

  • the remote API — callable through a proxy (yscene's 14 node_* slots, yrich document's scalar editing slots, ygui widget's manipulation methods);
  • the host SPI — virtual slots the host process invokes on the real object (the compositor calls render, the input statemachine calls process_input, the terminal implements the ytermsink callbacks). Never called through a proxy; from the remote side they do not exist.

The codebase already separates these structurally: yfigure:figure (all slots local) is the host SPI base; yscene:scene derives from it and adds the remote API. ytermsink:sink is a pure host-callback base the terminal derives from. The per-slot annotation is redundant with a structure the class graph already expresses.

A second consequence of the current default: wire exposure is what you get by forgetting an annotation. A new slot silently joins the RPC surface (stub RPC branch + skel entry) unless someone remembers local@. The wire surface is a compatibility and security contract; it must be opt-in.

Proposal: class-level locality, no per-slot markers

  1. Locality is declared per class, not per slot.
    • Default: local. No RPC branch, no skel entries, nothing on the wire. (Naming: an explicit class-level local@/norpc@ marker is unnecessary once local is the default; rpc@ is the only marker. "local" is preferred over "norpc" anywhere a negative form is needed transitionally, since it names what the thing is rather than what it lacks.)
    • rpc@<DOMAIN>:<CLASS> on the class struct (next to class@): every slot introduced by this class is wire-exposed.
  2. Inherited slots keep the base class's locality. A remotable class deriving from a local base is exactly the "host SPI + remote API" composition: figure's render/process_input stay host-side; scene's node_* go on the wire. No slot-level exceptions.
  3. Marshallability is validation, not annotation. An rpc@ class introducing a slot with unmarshallable arguments (raw pointers, in-process carriers) is a hard codegen error at that slot — not a silent local fallback. Today's local@ exists largely to dodge this; under the flip it becomes the guard rail.
  4. oneway@ folds into the rpc surface (a variant/flag of the wire form; only meaningful on rpc classes). The current "oneway@ + local@ is an error" check disappears structurally.
  5. Per-slot local@ is retired, not kept as an escape hatch. The three current stragglers are misplaced methods, not evidence for the mechanism:
    • yscene:set_registry binds a borrowed in-process registry pointer from the framework — host-side construction wiring; move it to the host create/configure path (or a host-only helper), off the class's remote surface.
    • ygui widget_paint / widget_emit_container / widget_emit_body — host paint SPI; move to a local base/mixin, same pattern as figure's render.
    • yrich document_render / document_apply_op — figure-side render contract; same treatment.

End state: the entire RPC surface of the codebase is the set of rpc@ classes — one greppable, auditable declaration per remotable class. Everything else generates zero wire code. ~190 per-slot lines and all the "every slot is local@" comments are deleted.

Migration sketch

  1. Introduce class-level rpc@<DOMAIN>:<CLASS> and inherited-locality resolution; keep per-slot local@ recognized. Annotate yscene, yrich document, ygui widget as rpc@. Generated output must be byte-identical for all-local classes; for the rpc classes it differs only where a per-slot annotation was missing/wrong — which is the audit.
  2. Move the three host-SPI/wiring stragglers off the remote classes (scene set_registry → host construction; widget paint/emit slots → local base/mixin; document render/apply_op → figure side).
  3. Flip the default to local for unannotated classes; per-slot local@ becomes a codegen error with a pointer at the class-level form; delete all per-slot lines.
  4. Fold oneway@ into the rpc form.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions