Start every render from the zero hold set

This commit is contained in:
2026-09-28 18:45:07 +02:00
committed by Lillem4n
parent 1d73f1dc90
commit 1b9cf00a26
6 changed files with 12 additions and 17 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ In [docs/decisions.md](docs/decisions.md):
- A render shares one reference draw per category, per group - A render shares one reference draw per category, per group
- A draw group name is local to its category - A draw group name is local to its category
- The expansion hold and the render's draws are two fences - The expansion hold and the render's draws are two fences
- A record makes its draw maps up front, a `Fake` on its first read - A hold set makes its draw maps on the first read
- A category never references itself, and a record's fences run at load - A category never references itself, and a record's fences run at load
- A record's column set is fixed before the first draw - A record's column set is fixed before the first draw
- Null is a `null` item, not a rate - Null is a `null` item, not a rate
+8 -7
View File
@@ -242,14 +242,15 @@ groups. They pin different things — an operand pins the value its own render p
and stops at a reference, a path pins every level it passes through — so one walk would and stops at a reference, a path pins every level it passes through — so one walk would
carry both rules and both scopes anyway, and tell them apart at every step. carry both rules and both scopes anyway, and tell them apart at every step.
## A record makes its draw maps up front, a `Fake` on its first read ## A hold set makes its draw maps on the first read
A record's columns always read through the render's draws, so making the maps where the Every render starts from the zero `holdSet`, per goal 2's one spelling. Making the maps
set is declared keeps them on that frame's stack. A `Fake` often reads no reference path where the set is declared kept a record's on its frame's stack, but a `Fake`'s set lives
at all, and making them anyway cost about a fifth of the cheapest render, so it makes on the `Generator`, so there it cost every render two heap allocations and about half the
them on the first read instead, at two heap allocations for a render that does share a cheapest one's time, measured 2026-09-28. Lazily, a render reading a reference path pays
draw. The allocation gate over a repeat of a reference path and over a named draw group those two allocations, a record or struct included, and one reading none pays nothing;
prices that, and pins what keeps a hold set off the heap. goal 11 holds either way. The allocation gate over a repeat of a reference path and over
a named draw group prices that, and pins what keeps a hold set off the heap.
## A category never references itself, and a record's fences run at load ## A category never references itself, and a record's fences run at load
-4
View File
@@ -20,10 +20,6 @@ type renderScope struct {
pins *pinSet pins *pinSet
} }
func eagerHoldSet() holdSet {
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]readValue{}}}
}
// renderOnce renders n as one render, over a hold set of its own. // renderOnce renders n as one render, over a hold set of its own.
func renderOnce(s *session, n node) string { func renderOnce(s *session, n node) string {
var set holdSet var set holdSet
+2 -2
View File
@@ -130,7 +130,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %s: %w", path, err) return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
} }
set := eagerHoldSet() var set holdSet
sc := renderScope{set: &set} sc := renderScope{set: &set}
if t, isTable := n.(*table); isTable { if t, isTable := n.(*table); isTable {
if n, err = tableRecord(f.rand, t, tail, sc); err != nil { if n, err = tableRecord(f.rand, t, tail, sc); err != nil {
@@ -204,7 +204,7 @@ type RecordTemplate struct {
func (t *RecordTemplate) Fake() *Record { func (t *RecordTemplate) Fake() *Record {
t.g.mu.Lock() t.g.mu.Lock()
defer t.g.mu.Unlock() defer t.g.mu.Unlock()
set := eagerHoldSet() var set holdSet
return renderRecord(t.g.rand, t.template, t.columns, renderScope{set: &set}) return renderRecord(t.g.rand, t.template, t.columns, renderScope{set: &set})
} }
+1 -1
View File
@@ -351,7 +351,7 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
// fill draws the record into v's tagged fields, then each nested struct as a record of its own. // fill draws the record into v's tagged fields, then each nested struct as a record of its own.
func (s *structShape) fill(sess *session, v reflect.Value) { func (s *structShape) fill(sess *session, v reflect.Value) {
if s.record != nil { if s.record != nil {
set := eagerHoldSet() var set holdSet
for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns { for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
setColumn(fieldAt(v, s.fieldIndexes[i]), c) setColumn(fieldAt(v, s.fieldIndexes[i]), c)
} }
-2
View File
@@ -21,8 +21,6 @@ Self-sufficiency 5.6), every seat capped by Locality, and all nine naming
This round, in order: This round, in order:
- Start every render from one spelling of an empty hold set: `Fake` uses
`holdSet{}`, the record and struct paths `eagerHoldSet()`.
- Correct the false comments: `funcCall` names `checkFunc` where `parseFormat` - Correct the false comments: `funcCall` names `checkFunc` where `parseFormat`
reports, `struct.go`'s "t sits at index within c.t" names no field, `recordFormats` reports, `struct.go`'s "t sits at index within c.t" names no field, `recordFormats`
calls ndjson "the same column", `Generator` says every other entry point takes `mu` calls ndjson "the same column", `Generator` says every other entry point takes `mu`