X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/80e348724041a694002fc38f57e8868b9b9a2be9..e86df5fad9aa2f280aad845adeefd239b0e944ae:/view.go?ds=inline
diff --git a/view.go b/view.go
index 1b3ed6a..9af1c21 100644
--- a/view.go
+++ b/view.go
@@ -88,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
@@ -191,76 +299,6 @@ func (v *View) LinesWithout(skip map[int]bool) []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
- }
-
- v.merging = false
-
- return merged, nil
-}
-
// renderLine writes one view line, "id+n|text", and a newline.
func (v *View) renderLine(text *strings.Builder, line NodeKey) {
body := viewPending
@@ -277,55 +315,26 @@ 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
- 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
- }
+// 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
- last := nodeEnd(right.level, right.index) - 1
- due := math.Ldexp(float64(v.end-last), -left.level)
- if best >= 0 && due <= bestDue {
- continue
- }
+ text.WriteString("\n")
- built, err := tree.Built(left.level+1, left.index/2)
- if err != nil {
- return -1, fmt.Errorf("check parent node: %w", err)
+ for _, line := range v.lines {
+ _, built := v.nodes[line]
+ if !built || nodeEnd(line.level, line.index) > end {
+ break
}
- if built {
- best = n
- bestDue = due
- }
+ v.renderLine(&text, line)
}
- 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)
- }
+ text.WriteString("")
- return node.Size
+ return text.String()
}
// restoreView returns the view of state with the limits high and low. Each line
@@ -341,7 +350,7 @@ func restoreView(state ViewState, high, low int, tree ViewNodeReader) (*View, er
built, err := v.load(tree, line)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("load restored view node: %w", err)
}
if line.level > 0 && !built {