Skip to content
Open

Nfs #129

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions docs/generated-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<!--
Copyright 2026 XTX Markets Technologies Limited

SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

# Using Generated Go Code

This document explains how to use the Go types generated by `msgc` from `.msg`
files. For the `.msg` language itself, see [msg-format.md](msg-format.md). For
internal codegen details, see [codegen-spec.md](codegen-spec.md).

## Quick start

```bash
go run ./cmd/msgc -pkg mypackage -o output.go input.msg
```

The generated code depends only on `encoding/binary` from the standard library.
Multiple generated files can coexist in the same package.

## Key concepts

Generated types operate directly on byte buffers with no intermediate Go
structs. **Readers** alias the input bytes (zero-copy). **Writers** build output
by appending to a `[]byte`. Both are lightweight views — one pointer for structs,
one slice header for messages — so pass them by value freely.

Since readers alias the buffer, modifying the underlying bytes changes what the
reader sees. If you need the data to outlive the buffer, copy it.

## Constants and enums

Constants become untyped Go constants. Enums become typed `const` declarations:

```go
const NFS4_FHSIZE = 128

const (
REC_READING uint32 = 1
REC_CONFIG uint32 = 2
REC_HEARTBEAT uint32 = 3
)
```

## Structs (fixed-size)

```go
// Reading:
v, ok := ReadVec3i16(buf) // ok=false if buf too short
fmt.Println(v.X(), v.Y(), v.Z()) // fields accessible in any order

// Writing:
buf, v := StartVec3i16(buf)
v.SetX(-100)
v.SetY(200)
// bytes are live in buf immediately — no Finish needed
```

Struct getters/setters also work for embedded structs, fixed-size arrays, and
padded strings (`npchar`/`spchar`):

```go
r.Header().Sensor().SetKind(SENS_ACCEL) // nested struct access
ac.Cal(0) // read array element 0
ac.SetCal(2, 100) // write array element 2
dl.SetName("hello") // writes "hello" + padding
```

## Messages (variable-size)

### Reading

```go
msg, ok := ReadSampleList(buf) // validates structure, returns typed view
msg.Count() // scalar header field
msg.Samples(0) // indexed array access (fixed-size elements)
```

Variable-size array elements use iterators:

```go
it := lf.Records()
for it.Next() {
entry := it.Record() // current element
}
```

### Writing

The basic pattern is `Start` → set/append fields → `Finish`:

```go
w := StartSampleList(nil) // nil allocates; or pass existing []byte
w.AppendSamples(100)
w.AppendSamples(200)
buf := w.Finish() // patches count, returns completed buffer
```

When a message contains variable-size children (embedded messages, variable-size
array elements), writing them produces a **child writer** that must be finished
and resumed before the parent can continue:

```go
w := StartStampedData(nil)
w.Ts().SetNanos(1234567890)
child := w.StartPayload()
child.SetData([]byte{0xAA})
w.Resume(child.Finish()) // REQUIRED — parent is invalid until Resume
buf := w.Finish()
```

The same pattern applies to variable-size array elements: each `Append` returns
a child writer.

### Unions

Read the discriminant, then call the `As` method for the appropriate arm:

```go
switch entry.Rtype() {
case REC_READING:
r := entry.Body().AsSingleReading()
fmt.Println(r.Value())
case REC_CONFIG:
c := entry.Body().AsConfigRecord()
fmt.Println(c.SampleRate())
}
```

`As` methods check the discriminant at runtime — calling the wrong one panics.

Union arms are written via `Set{Field}_{Arm}()` methods on the enclosing
writer. Fixed-size arms return a struct to fill in place; variable-size arms
return a child writer that must be finished and resumed:

```go
// Fixed-size arm:
w := StartRecordEntry(nil)
sr := w.SetBody_Reading()
sr.SetValue(42)
buf := w.Finish()

// Variable-size arm:
w := StartRecordEntry(nil)
cfgW := w.SetBody_Config()
cfgW.SetSampleRate(48000)
w.Resume(cfgW.Finish())
buf := w.Finish()
```

When a message has an array of union elements, the parent writer provides
combined `Append{Array}_{Arm}()` methods that set the discriminant
automatically.

## Safety

**Readers** are safe to use freely. `Read` validates input bounds; getters on a
valid reader cannot panic. Readers never modify the buffer.

**Writers** have a few rules:

1. **Call `Finish()`.** It patches deferred fields (counts, extent lengths).
2. **Call `Resume(child.Finish())` for variable-size children.** The parent
panics if used while a child is active.
3. **Fill struct accessors before the next append.** A subsequent append may
reallocate the buffer and invalidate the struct pointer.
4. **Write arrays in declaration order** for multi-array messages.

| Detected at runtime (panics) | Not detected (silent bugs) |
|------------------------------|---------------------------|
| Parent used while child is active | Forgetting top-level `Finish()` |
| Missing `Resume(child.Finish())` | Holding a struct accessor across a reallocation |
| Appending arrays out of order | |
Loading
Loading