X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/4a931bccab5879ab708647b2df8dadc052f128bf..98d137c3e945a09f76e85bd18238558c668fdca4:/view.go?ds=sidebyside diff --git a/view.go b/view.go index 233e089..9af1c21 100644 --- a/view.go +++ b/view.go @@ -23,42 +23,6 @@ const ( viewPending = "(not summarized yet: zoom it)" ) -// newView returns an empty view with the limits high and low. -func newView(high, low int) *View { - return &View{ - high: high, - low: low, - nodes: make(map[NodeKey]Node), - } -} - -// restoreView returns the view of state with the limits high and low. Each line -// must continue the one before it, and each line above level 0 must be built. -func restoreView(state ViewState, high, low int, tree ViewNodeReader) (*View, error) { - v := newView(high, low) - v.merging = state.Merging - for _, pair := range state.Lines { - line := NodeKey{pair[0], pair[1]} - if line.level < 0 || line.level >= bits.UintSize-2 || line.index < 0 || line.index< 0 && !built { - return nil, fmt.Errorf("view line (%d, %d) is not built", line.level, line.index) - } - - v.lines = append(v.lines, line) - v.end = nodeEnd(line.level, line.index) - } - - return v, nil -} - // View is a list of tree nodes that covers messages 0 to end-1 without a gap, // oldest first. Each new message appends its leaf as a line. Once the size of // the view goes above high, one batch merges the most due sibling pairs until @@ -83,6 +47,27 @@ type View struct { merging bool } +// ViewState is the saved form of a View. The limits are not part of it. +type ViewState struct { + Lines [][2]int `json:"lines"` + Merging bool `json:"merging"` +} + +// ViewNodeReader gives the built nodes of a tree. +type ViewNodeReader interface { + Built(level, index int) (bool, error) + Node(level, index int) (Node, bool, error) +} + +// newView returns an empty view with the limits high and low. +func newView(high, low int) *View { + return &View{ + high: high, + low: low, + nodes: make(map[NodeKey]Node), + } +} + // State returns the saved form of the view. func (v *View) State() ViewState { state := ViewState{ @@ -103,13 +88,121 @@ func (v *View) Append(tree ViewNodeReader) (bool, error) { line := NodeKey{0, v.end} _, err := v.load(tree, line) if err != nil { - return false, err + return false, fmt.Errorf("load appended leaf: %w", err) } v.lines = append(v.lines, line) v.end++ - return v.fit(tree) + merged, err := v.fit(tree) + if err != nil { + return merged, fmt.Errorf("fit appended view: %w", err) + } + + return merged, nil +} + +// load keeps the node of line when it is built, and tells if it is. +func (v *View) load(tree ViewNodeReader, line NodeKey) (bool, error) { + node, built, err := tree.Node(line.level, line.index) + if err != nil || !built { + return false, err + } + + v.nodes[line] = node + + return true, nil +} + +// fit merges the most due pairs while a batch runs, and tells if a merge +// changed the view. +func (v *View) fit(tree ViewNodeReader) (bool, error) { + size := v.Size() + if size > v.high { + v.merging = true + } + + merged := false + for v.merging && size > v.low { + best, err := v.mostDue(tree) + if err != nil || best < 0 { + if err != nil { + return merged, fmt.Errorf("choose merge candidate: %w", err) + } + + return merged, nil + } + + left, right := v.lines[best], v.lines[best+1] + parent := NodeKey{left.level + 1, left.index / 2} + _, err = v.load(tree, parent) + if err != nil { + return merged, fmt.Errorf("load merged parent: %w", err) + } + + size += v.lineSize(parent) - v.lineSize(left) - v.lineSize(right) + delete(v.nodes, left) + delete(v.nodes, right) + + v.lines[best] = parent + v.lines = append(v.lines[:best+1], v.lines[best+2:]...) + merged = true + } + + v.merging = false + + return merged, nil +} + +// Size returns the sum of the line sizes. +func (v *View) Size() int { + size := 0 + for _, line := range v.lines { + size += v.lineSize(line) + } + + return size +} + +// mostDue returns the position of the left line of the most due sibling pair +// whose parent is built, or -1 when there is no such pair. +func (v *View) mostDue(tree ViewNodeReader) (int, error) { + best := -1 + bestDue := 0.0 + for n := 0; n+1 < len(v.lines); n++ { + left, right := v.lines[n], v.lines[n+1] + if left.index%2 != 0 || right.level != left.level || right.index != left.index+1 { + continue + } + + last := nodeEnd(right.level, right.index) - 1 + due := math.Ldexp(float64(v.end-last), -left.level) + if best >= 0 && due <= bestDue { + continue + } + + built, err := tree.Built(left.level+1, left.index/2) + if err != nil { + return -1, fmt.Errorf("check parent node: %w", err) + } + + if built { + best = n + bestDue = due + } + } + + return best, nil +} + +// lineSize returns the size of a view line. +func (v *View) lineSize(line NodeKey) int { + node, built := v.nodes[line] + if !built { + return len(viewPending) + } + + return node.Size } // Coarsen takes each merge of other, a view of the same messages, and then @@ -197,78 +290,13 @@ func (v *View) LinesWithout(skip map[int]bool) []string { } var text strings.Builder - v.renderLine(&text, line) - lines = append(lines, text.String()) - } - - return lines -} - -// Context returns the lines of the view before message end as the model reads -// them. It stops at the first line that is not built, so a compaction never -// sees a pending mark or a part of a message. -func (v *View) Context(end int) string { - var text strings.Builder - text.WriteString("\n") - for _, line := range v.lines { - _, built := v.nodes[line] - if !built || nodeEnd(line.level, line.index) > end { - break - } v.renderLine(&text, line) - } - - text.WriteString("") - - return text.String() -} - -// load keeps the node of line when it is built, and tells if it is. -func (v *View) load(tree ViewNodeReader, line NodeKey) (bool, error) { - node, built, err := tree.Node(line.level, line.index) - if err != nil || !built { - return false, err - } - - v.nodes[line] = node - - return true, nil -} - -// fit merges the most due pairs while a batch runs, and tells if a merge -// changed the view. -func (v *View) fit(tree ViewNodeReader) (bool, error) { - size := v.Size() - if size > v.high { - v.merging = true - } - - merged := false - for v.merging && size > v.low { - best, err := v.mostDue(tree) - if err != nil || best < 0 { - return merged, err - } - - left, right := v.lines[best], v.lines[best+1] - parent := NodeKey{left.level + 1, left.index / 2} - _, err = v.load(tree, parent) - if err != nil { - return merged, err - } - size += v.lineSize(parent) - v.lineSize(left) - v.lineSize(right) - delete(v.nodes, left) - delete(v.nodes, right) - v.lines[best] = parent - v.lines = append(v.lines[:best+1], v.lines[best+2:]...) - merged = true + lines = append(lines, text.String()) } - v.merging = false - - return merged, nil + return lines } // renderLine writes one view line, "id+n|text", and a newline. @@ -287,65 +315,51 @@ func (v *View) renderLine(text *strings.Builder, line NodeKey) { text.WriteByte('\n') } -// Size returns the sum of the line sizes. -func (v *View) Size() int { - size := 0 +// Context returns the lines of the view before message end as the model reads +// them. It stops at the first line that is not built, so a compaction never +// sees a pending mark or a part of a message. +func (v *View) Context(end int) string { + var text strings.Builder + + text.WriteString("\n") + for _, line := range v.lines { - size += v.lineSize(line) + _, built := v.nodes[line] + if !built || nodeEnd(line.level, line.index) > end { + break + } + + v.renderLine(&text, line) } - return size -} + text.WriteString("") -// mostDue returns the position of the left line of the most due sibling pair -// whose parent is built, or -1 when there is no such pair. -func (v *View) mostDue(tree ViewNodeReader) (int, error) { - best := -1 - bestDue := 0.0 - for n := 0; n+1 < len(v.lines); n++ { - left, right := v.lines[n], v.lines[n+1] - if left.index%2 != 0 || right.level != left.level || right.index != left.index+1 { - continue - } + return text.String() +} - last := nodeEnd(right.level, right.index) - 1 - due := math.Ldexp(float64(v.end-last), -left.level) - if best >= 0 && due <= bestDue { - continue +// restoreView returns the view of state with the limits high and low. Each line +// must continue the one before it, and each line above level 0 must be built. +func restoreView(state ViewState, high, low int, tree ViewNodeReader) (*View, error) { + v := newView(high, low) + v.merging = state.Merging + for _, pair := range state.Lines { + line := NodeKey{pair[0], pair[1]} + if line.level < 0 || line.level >= bits.UintSize-2 || line.index < 0 || line.index< 0 && !built { + return nil, fmt.Errorf("view line (%d, %d) is not built", line.level, line.index) } - } - return best, nil -} - -// lineSize returns the size of a view line. -func (v *View) lineSize(line NodeKey) int { - node, built := v.nodes[line] - if !built { - return len(viewPending) + v.lines = append(v.lines, line) + v.end = nodeEnd(line.level, line.index) } - return node.Size -} - -// ViewState is the saved form of a View. The limits are not part of it. -type ViewState struct { - Lines [][2]int `json:"lines"` - Merging bool `json:"merging"` -} - -// ViewNodeReader gives the built nodes of a tree. -type ViewNodeReader interface { - Built(level, index int) (bool, error) - Node(level, index int) (Node, bool, error) + return v, nil }