X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/e5343bd4c3ec69192e92f4be339b855eef36b6ec..4b9b22619008f0829b7f5a8685e7b1d2a9aeb6bb:/view.go?ds=sidebyside diff --git a/view.go b/view.go index ddd97ed..70182d0 100644 --- a/view.go +++ b/view.go @@ -24,7 +24,8 @@ const ( // nodeReader gives the built nodes of a tree. type nodeReader interface { - Node(level, index int) (Node, bool) + Built(level, index int) (bool, error) + Node(level, index int) (Node, bool, error) } // View is a list of tree nodes that covers messages 0 to end-1 without a gap, @@ -33,18 +34,20 @@ type nodeReader interface { // the size is at most low. A batch merges only pairs whose parent is built. If // the batch cannot get to low, it continues at each new message until it does. // -// The size of a line is the size of its node text, or the size of the pending -// text when the node is not built. +// The view keeps the nodes of its built lines in memory, so its size and its +// text need no read of the tree. The size of a line is the size of its node +// text, or the size of the pending text when the node is not built. // // For the sibling pair (l, i) and (l, i+1), due is (T - last) / 2^l, with T the // number of messages and last the ID of the last message of the pair. That is // how long ago the pair ended, measured in its own line size. With a budget of -// the length of Taelin's rollback push list, this order makes the same merges -// as the push. The oldest pair wins a tie. +// the length of the rollback push list of the reference design, this order +// makes the same merges as the push. The oldest pair wins a tie. type View struct { high int low int lines []nodeKey + nodes map[nodeKey]Node end int merging bool } @@ -58,8 +61,9 @@ type viewState struct { // newView returns an empty view with the limits high and low. func newView(high, low int) *View { return &View{ - high: high, - low: low, + high: high, + low: low, + nodes: make(map[nodeKey]Node), } } @@ -74,7 +78,11 @@ func restoreView(state viewState, high, low int, tree nodeReader) (*View, error) return nil, fmt.Errorf("view line (%d, %d) does not start at message %d", line.level, line.index, v.end) } - _, built := tree.Node(line.level, line.index) + built, err := v.load(tree, line) + if err != nil { + return nil, err + } + if line.level > 0 && !built { return nil, fmt.Errorf("view line (%d, %d) is not built", line.level, line.index) } @@ -86,6 +94,18 @@ func restoreView(state viewState, high, low int, tree nodeReader) (*View, error) return v, nil } +// load keeps the node of line when it is built, and tells if it is. +func (v *View) load(tree nodeReader, 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 +} + // State returns the saved form of the view. func (v *View) State() viewState { state := viewState{ @@ -102,8 +122,14 @@ func (v *View) State() viewState { // Append adds the leaf of the next message as a line and merges when the view // 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}) +func (v *View) Append(tree nodeReader) (bool, error) { + line := nodeKey{0, v.end} + _, err := v.load(tree, line) + if err != nil { + return false, err + } + + v.lines = append(v.lines, line) v.end++ return v.fit(tree) @@ -111,22 +137,29 @@ func (v *View) Append(tree nodeReader) bool { // 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) +func (v *View) fit(tree nodeReader) (bool, error) { + size := v.Size() if size > v.high { v.merging = true } merged := false for v.merging && size > v.low { - best := v.mostDue(tree) - if best < 0 { - return merged + 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} - size += lineSize(tree, parent) - lineSize(tree, left) - lineSize(tree, right) + _, 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 @@ -134,7 +167,7 @@ func (v *View) fit(tree nodeReader) bool { v.merging = false - return merged + return merged, nil } // Coarsen takes each merge of other, a view of the same messages, and then @@ -142,16 +175,23 @@ func (v *View) fit(tree nodeReader) bool { // 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) { +func (v *View) Coarsen(other *View, tree nodeReader) error { lines := make([]nodeKey, 0, len(v.lines)) + nodes := make(map[nodeKey]Node, len(v.nodes)) mine, theirs := 0, 0 for start := 0; start < v.end; { line := v.lines[mine] + node, built := v.nodes[line] if other.lines[theirs].level > line.level { line = other.lines[theirs] + node, built = other.nodes[line] } lines = append(lines, line) + if built { + nodes[line] = node + } + start = nodeEnd(line.level, line.index) for mine < len(v.lines) && v.lines[mine].index<= 0 && due <= bestDue { continue } - last := nodeEnd(right.level, right.index) - 1 - due := math.Ldexp(float64(v.end-last), -left.level) - if best < 0 || due > bestDue { + built, err := tree.Built(left.level+1, left.index/2) + if err != nil { + return -1, err + } + + if built { best = n bestDue = due } } - return best + return best, nil +} + +// Refresh keeps node when it is the node of a pending line. +func (v *View) Refresh(node Node) { + key := nodeKey{node.Level, node.Index} + for n := len(v.lines) - 1; n >= 0; n-- { + line := v.lines[n] + if line == key { + v.nodes[key] = node + return + } + + if line.index< tags, with the newlines of the text as spaces. -func (v *View) Render(tree nodeReader) string { +func (v *View) Render() string { var text strings.Builder text.WriteString("\n") for _, line := range v.lines { - renderLine(&text, tree, line) + v.renderLine(&text, line) } text.WriteString("") @@ -218,48 +291,38 @@ func (v *View) Render(tree nodeReader) string { return text.String() } +// renderLine writes one view line, "id+n|text", and a newline. +func (v *View) renderLine(text *strings.Builder, line nodeKey) { + body := pending + node, built := v.nodes[line] + if built { + body = strings.ReplaceAll(node.Text, "\n", " ") + } + + text.WriteString(strconv.Itoa(line.index << line.level)) + text.WriteByte('+') + text.WriteString(strconv.Itoa(1 << line.level)) + text.WriteByte('|') + text.WriteString(body) + text.WriteByte('\n') +} + // 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 { +func (v *View) Context(end int) string { var text strings.Builder text.WriteString("\n") for _, line := range v.lines { - _, built := tree.Node(line.level, line.index) + _, built := v.nodes[line] if !built || nodeEnd(line.level, line.index) > end { break } - renderLine(&text, tree, line) + v.renderLine(&text, line) } text.WriteString("") 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) - if !built { - return len(pending) - } - - return node.Size -} - -// renderLine writes one view line, "id+n|text", and a newline. -func renderLine(text *strings.Builder, tree nodeReader, line nodeKey) { - body := pending - node, built := tree.Node(line.level, line.index) - if built { - body = strings.ReplaceAll(node.Text, "\n", " ") - } - - text.WriteString(strconv.Itoa(line.index << line.level)) - text.WriteByte('+') - text.WriteString(strconv.Itoa(1 << line.level)) - text.WriteByte('|') - text.WriteString(body) - text.WriteByte('\n') -}