]> git.giacomolli.uk - onechat/blobdiff - view.go
Build the nodes from a queue in the background
[onechat] / view.go
diff --git a/view.go b/view.go
index 158b59e65a130df5def07ed10ea6e32d53a87fd4..ddd97ed55f404e0a8b6ede25ea3ffccbe4883b57 100644 (file)
--- a/view.go
+++ b/view.go
@@ -13,6 +13,11 @@ const (
        chatHigh = 128000
        chatLow  = 64000
 
        chatHigh = 128000
        chatLow  = 64000
 
+       // compactionHigh and compactionLow are the limits of the compaction
+       // view in bytes.
+       compactionHigh = 32000
+       compactionLow  = 16000
+
        // pending is the text of a line that is not built yet.
        pending = "(not summarized yet: zoom it)"
 )
        // pending is the text of a line that is not built yet.
        pending = "(not summarized yet: zoom it)"
 )
@@ -96,23 +101,27 @@ func (v *View) State() viewState {
 }
 
 // Append adds the leaf of the next message as a line and merges when the view
 }
 
 // Append adds the leaf of the next message as a line and merges when the view
-// is too large.
-func (v *View) Append(tree nodeReader) {
+// is too large. It tells if a merge changed the view.
+func (v *View) Append(tree nodeReader) bool {
        v.lines = append(v.lines, nodeKey{0, v.end})
        v.end++
        v.lines = append(v.lines, nodeKey{0, v.end})
        v.end++
-       v.fit(tree)
+
+       return v.fit(tree)
 }
 
 }
 
-func (v *View) fit(tree nodeReader) {
+// fit merges the most due pairs while a batch runs, and tells if a merge
+// changed the view.
+func (v *View) fit(tree nodeReader) bool {
        size := v.Size(tree)
        if size > v.high {
                v.merging = true
        }
 
        size := v.Size(tree)
        if size > v.high {
                v.merging = true
        }
 
+       merged := false
        for v.merging && size > v.low {
                best := v.mostDue(tree)
                if best < 0 {
        for v.merging && size > v.low {
                best := v.mostDue(tree)
                if best < 0 {
-                       return
+                       return merged
                }
 
                left, right := v.lines[best], v.lines[best+1]
                }
 
                left, right := v.lines[best], v.lines[best+1]
@@ -120,9 +129,42 @@ func (v *View) fit(tree nodeReader) {
                size += lineSize(tree, parent) - lineSize(tree, left) - lineSize(tree, right)
                v.lines[best] = parent
                v.lines = append(v.lines[:best+1], v.lines[best+2:]...)
                size += lineSize(tree, parent) - lineSize(tree, left) - lineSize(tree, right)
                v.lines[best] = parent
                v.lines = append(v.lines[:best+1], v.lines[best+2:]...)
+               merged = true
        }
 
        v.merging = false
        }
 
        v.merging = false
+
+       return merged
+}
+
+// Coarsen takes each merge of other, a view of the same messages, and then
+// merges in one batch until the size is at most low. Each line of the result is
+// the coarser of the two lines that start at its first message. Dyadic ranges
+// either nest or do not overlap, so the result covers the messages without a
+// gap.
+func (v *View) Coarsen(other *View, tree nodeReader) {
+       lines := make([]nodeKey, 0, len(v.lines))
+       mine, theirs := 0, 0
+       for start := 0; start < v.end; {
+               line := v.lines[mine]
+               if other.lines[theirs].level > line.level {
+                       line = other.lines[theirs]
+               }
+
+               lines = append(lines, line)
+               start = nodeEnd(line.level, line.index)
+               for mine < len(v.lines) && v.lines[mine].index<<v.lines[mine].level < start {
+                       mine++
+               }
+
+               for theirs < len(other.lines) && other.lines[theirs].index<<other.lines[theirs].level < start {
+                       theirs++
+               }
+       }
+
+       v.lines = lines
+       v.merging = true
+       v.fit(tree)
 }
 
 // mostDue returns the position of the left line of the most due sibling pair
 }
 
 // mostDue returns the position of the left line of the most due sibling pair
@@ -176,6 +218,26 @@ func (v *View) Render(tree nodeReader) string {
        return text.String()
 }
 
        return text.String()
 }
 
+// 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(tree nodeReader, end int) string {
+       var text strings.Builder
+       text.WriteString("<chat>\n")
+       for _, line := range v.lines {
+               _, built := tree.Node(line.level, line.index)
+               if !built || nodeEnd(line.level, line.index) > end {
+                       break
+               }
+
+               renderLine(&text, tree, line)
+       }
+
+       text.WriteString("</chat>")
+
+       return text.String()
+}
+
 // lineSize returns the size of a view line.
 func lineSize(tree nodeReader, line nodeKey) int {
        node, built := tree.Node(line.level, line.index)
 // lineSize returns the size of a view line.
 func lineSize(tree nodeReader, line nodeKey) int {
        node, built := tree.Node(line.level, line.index)