Across a process
Everything myelin takes and gives back is a plain value, so it can cross a process. A keymap built in one process can be matched in another, and a menu can be drawn by a front end written in another language: a web page, a phone's native sheet, a plugin.
What crosses
The data types have no methods except their generated
msgp codec, EncodeMsg and
DecodeMsg:
- the schema:
SchemaandNodeType; - the keymap's documents:
Doc,Bind,GroupandFence; - where the focus is:
PathandStep; - the answers:
Binding,Match,Candidate,Problem,MenuandItem; - the sequencer:
PendingandResult.
Env, Keymap and Context hold compiled CEL, so they stay in the
process that built them. A process that has the schema and the documents
builds a keymap of its own from them.
On the wire
Each value is a msgpack map keyed by its fields' names, so a reader in any
language finds a field by name. A deadline is a msgpack timestamp
(extension −1), in UTC. Write values to the connection through a
msgp.Writer and read them through a msgp.Reader, one value after
another, with no buffer of the whole message in between:
env, err := myelin.NewEnv(myelin.Schema{
Nodes: []myelin.NodeType{{Name: "list"}},
Layers: []string{"defaults"},
Aliases: map[string]string{"leader": "space"},
})
if err != nil {
panic(err)
}
km, problems := env.Build([]myelin.Doc{{Layer: "defaults",
Binds: []myelin.Bind{
{Keys: "<leader> n", Action: "file.new", Label: "New file"},
{Keys: "<leader> q", Action: "quit", Label: "Quit"},
},
}}, nil)
if km == nil {
panic(fmt.Sprint(problems))
}
// 1. The program writes the leader's menu to the connection.
var conn bytes.Buffer // a socket or a pipe in a real program
w := msgp.NewWriter(&conn)
leader, _ := km.Alias("leader")
menu := km.Menu(leader, myelin.Path{{Name: "list"}}, nil)
if err := menu.EncodeMsg(w); err != nil {
panic(err)
}
if err := w.Flush(); err != nil {
panic(err)
}
// 2. The front end reads it, with limits for bytes it didn't write.
r := msgp.NewReader(&conn)
r.SetMaxRecursionDepth(32)
r.SetMaxStringLength(4096)
var got myelin.Menu
if err := got.DecodeMsg(r); err != nil {
panic(err)
}
for _, it := range got.Items {
fmt.Println(myelinkey.Display(it.Key), it.Label)
}
// Output:
// n New file
// q Quit
Menus nest, a level inside each group's item in a tree, so give the reader a recursion limit that fits your deepest menu.
Limits on reading
Decoding is bounded for bytes you didn't write yourself:
- an array holds at most 1,024 elements;
- a map holds at most 256 entries;
- a document holds at most 16,384 bindings.
More fails with msgp.ErrLimitExceeded before anything is allocated for
it. Strings and a step's attribute values are bounded by the reader. When
the bytes come from a peer, set SetMaxStringLength, SetMaxElements and
SetMaxRecursionDepth on your msgp.Reader.
Next
- Performance: what a key, a match and a menu cost.
- The data types' fields are on pkg.go.dev.