mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-10 01:51:07 +09:00
353 lines
6.9 KiB
Go
353 lines
6.9 KiB
Go
package change
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/anytypeio/go-anytype-middleware/util/slice"
|
|
)
|
|
|
|
type Mode int
|
|
|
|
const (
|
|
Append Mode = iota
|
|
Rebuild
|
|
Nothing
|
|
)
|
|
|
|
func NewTree() *Tree {
|
|
return &Tree{}
|
|
}
|
|
|
|
func NewMetaTree() *Tree {
|
|
return &Tree{metaOnly: true}
|
|
}
|
|
|
|
type Tree struct {
|
|
root *Change
|
|
headIds []string
|
|
metaHeadIds []string
|
|
attached map[string]*Change
|
|
unAttached map[string]*Change
|
|
// missed id -> list of dependency ids
|
|
waitList map[string][]string
|
|
metaOnly bool
|
|
|
|
// bufs
|
|
iterCompBuf []*Change
|
|
iterQueue []*Change
|
|
|
|
duplicateEvents int
|
|
}
|
|
|
|
func (t *Tree) RootId() string {
|
|
if t.root != nil {
|
|
return t.root.Id
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (t *Tree) Root() *Change {
|
|
return t.root
|
|
}
|
|
|
|
func (t *Tree) AddFast(changes ...*Change) {
|
|
for _, c := range changes {
|
|
// ignore existing
|
|
if _, ok := t.attached[c.Id]; ok {
|
|
continue
|
|
} else if _, ok := t.unAttached[c.Id]; ok {
|
|
continue
|
|
}
|
|
t.add(c)
|
|
}
|
|
t.updateHeads(changes)
|
|
}
|
|
|
|
func (t *Tree) Add(changes ...*Change) (mode Mode) {
|
|
var beforeHeadIds = t.headIds
|
|
var attached bool
|
|
var empty = t.Len() == 0
|
|
for _, c := range changes {
|
|
// ignore existing
|
|
if _, ok := t.attached[c.Id]; ok {
|
|
continue
|
|
} else if _, ok := t.unAttached[c.Id]; ok {
|
|
continue
|
|
}
|
|
if t.add(c) {
|
|
attached = true
|
|
}
|
|
}
|
|
if !attached {
|
|
return Nothing
|
|
}
|
|
t.updateHeads(changes)
|
|
if empty {
|
|
return Rebuild
|
|
}
|
|
for _, hid := range beforeHeadIds {
|
|
for _, newCh := range changes {
|
|
if _, ok := t.attached[newCh.Id]; ok {
|
|
if !t.after(newCh.Id, hid) {
|
|
return Rebuild
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Append
|
|
}
|
|
|
|
func (t *Tree) add(c *Change) (attached bool) {
|
|
if c == nil {
|
|
return false
|
|
}
|
|
if t.metaOnly {
|
|
c.PreviousIds = c.PreviousMetaIds
|
|
}
|
|
if t.root == nil { // first element
|
|
t.root = c
|
|
t.attached = map[string]*Change{
|
|
c.Id: c,
|
|
}
|
|
t.unAttached = make(map[string]*Change)
|
|
t.waitList = make(map[string][]string)
|
|
return true
|
|
}
|
|
if len(c.PreviousIds) > 1 {
|
|
sort.Strings(c.PreviousIds)
|
|
}
|
|
for _, pid := range c.PreviousIds {
|
|
if prev, ok := t.attached[pid]; ok {
|
|
prev.Next = append(prev.Next, c)
|
|
attached = true
|
|
if len(prev.Next) > 1 {
|
|
sort.Sort(sortChanges(prev.Next))
|
|
}
|
|
} else if prev, ok := t.unAttached[pid]; ok {
|
|
prev.Next = append(prev.Next, c)
|
|
if len(prev.Next) > 1 {
|
|
sort.Sort(sortChanges(prev.Next))
|
|
}
|
|
} else {
|
|
wl := t.waitList[pid]
|
|
wl = append(wl, c.Id)
|
|
t.waitList[pid] = wl
|
|
}
|
|
}
|
|
if attached {
|
|
t.attach(c, true)
|
|
} else {
|
|
t.unAttached[c.Id] = c
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *Tree) attach(c *Change, newEl bool) {
|
|
if _, ok := t.attached[c.Id]; ok {
|
|
return
|
|
}
|
|
t.attached[c.Id] = c
|
|
if !newEl {
|
|
delete(t.unAttached, c.Id)
|
|
}
|
|
for _, next := range c.Next {
|
|
t.attach(next, false)
|
|
}
|
|
if waitIds, ok := t.waitList[c.Id]; ok {
|
|
for _, wid := range waitIds {
|
|
next := t.unAttached[wid]
|
|
if next == nil {
|
|
next = t.attached[wid]
|
|
}
|
|
c.Next = append(c.Next, next)
|
|
if len(c.Next) > 1 {
|
|
sort.Sort(sortChanges(c.Next))
|
|
}
|
|
t.attach(next, false)
|
|
}
|
|
delete(t.waitList, c.Id)
|
|
}
|
|
}
|
|
|
|
func (t *Tree) after(id1, id2 string) (found bool) {
|
|
t.iterate(t.attached[id2], func(c *Change) (isContinue bool) {
|
|
if c.Id == id1 {
|
|
found = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return
|
|
}
|
|
|
|
func (t *Tree) recalculateHeads() (heads []string, metaHeads []string) {
|
|
start := time.Now()
|
|
total := 0
|
|
t.iterate(t.root, func(c *Change) (isContinue bool) {
|
|
total++
|
|
if len(c.Next) == 0 {
|
|
heads = append(heads, c.Id)
|
|
}
|
|
if c.HasMeta() {
|
|
for _, prevDetId := range c.PreviousMetaIds {
|
|
metaHeads = slice.Remove(metaHeads, prevDetId)
|
|
}
|
|
metaHeads = append(metaHeads, c.Id)
|
|
}
|
|
return true
|
|
})
|
|
if time.Since(start) > time.Millisecond*100 {
|
|
log.Errorf("recalculateHeads took %s for %d changes", time.Since(start), total)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (t *Tree) updateHeads(chs []*Change) {
|
|
var newHeadIds, newMetaHeadIds []string
|
|
if len(chs) == 1 && slice.UnsortedEquals(chs[0].PreviousIds, t.headIds) {
|
|
// shortcut when adding to the top of the tree
|
|
// only cover edge case when adding one change, otherwise it's not worth it
|
|
newHeadIds = []string{chs[0].Id}
|
|
}
|
|
if len(chs) == 1 && chs[0].HasMeta() && slice.UnsortedEquals(chs[0].PreviousMetaIds, t.metaHeadIds) {
|
|
// shortcut when adding to the top of the tree
|
|
// only cover edge case when adding one change, otherwise it's not worth it
|
|
newMetaHeadIds = []string{chs[0].Id}
|
|
}
|
|
|
|
if newHeadIds == nil {
|
|
newHeadIds, newMetaHeadIds = t.recalculateHeads()
|
|
}
|
|
if newHeadIds != nil {
|
|
t.headIds = newHeadIds
|
|
sort.Strings(t.headIds)
|
|
}
|
|
|
|
if newMetaHeadIds != nil {
|
|
t.metaHeadIds = newMetaHeadIds
|
|
sort.Strings(t.metaHeadIds)
|
|
}
|
|
}
|
|
|
|
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
|
it := newIterator()
|
|
defer freeIterator(it)
|
|
it.iterate(start, f)
|
|
}
|
|
|
|
func (t *Tree) Iterate(startId string, f func(c *Change) (isContinue bool)) {
|
|
t.iterate(t.attached[startId], f)
|
|
}
|
|
|
|
func (t *Tree) IterateBranching(startId string, f func(c *Change, branchLevel int) (isContinue bool)) {
|
|
// branchLevel indicates the number of parallel branches
|
|
var bc int
|
|
t.iterate(t.attached[startId], func(c *Change) (isContinue bool) {
|
|
if pl := len(c.PreviousIds); pl > 1 {
|
|
bc -= pl - 1
|
|
}
|
|
bl := bc
|
|
if nl := len(c.Next); nl > 1 {
|
|
bc += nl - 1
|
|
}
|
|
return f(c, bl)
|
|
})
|
|
}
|
|
|
|
func (t *Tree) Hash() string {
|
|
h := md5.New()
|
|
n := 0
|
|
t.iterate(t.root, func(c *Change) (isContinue bool) {
|
|
n++
|
|
fmt.Fprintf(h, "-%s", c.Id)
|
|
return true
|
|
})
|
|
return fmt.Sprintf("%d-%x", n, h.Sum(nil))
|
|
}
|
|
|
|
func (t *Tree) GetDuplicateEvents() int {
|
|
return t.duplicateEvents
|
|
}
|
|
|
|
func (t *Tree) ResetDuplicateEvents() {
|
|
t.duplicateEvents = 0
|
|
}
|
|
|
|
func (t *Tree) Len() int {
|
|
return len(t.attached)
|
|
}
|
|
|
|
func (t *Tree) Heads() []string {
|
|
return t.headIds
|
|
}
|
|
|
|
func (t *Tree) MetaHeads() []string {
|
|
return t.metaHeadIds
|
|
}
|
|
|
|
func (t *Tree) String() string {
|
|
var buf = bytes.NewBuffer(nil)
|
|
t.Iterate(t.RootId(), func(c *Change) (isContinue bool) {
|
|
buf.WriteString(c.Id)
|
|
if len(c.Next) > 1 {
|
|
buf.WriteString("-<")
|
|
} else if len(c.Next) > 0 {
|
|
buf.WriteString("->")
|
|
} else {
|
|
buf.WriteString("-|")
|
|
}
|
|
return true
|
|
})
|
|
return buf.String()
|
|
}
|
|
|
|
func (t *Tree) Get(id string) *Change {
|
|
return t.attached[id]
|
|
}
|
|
|
|
func (t *Tree) LastSnapshotId(ctx context.Context) string {
|
|
var sIds []string
|
|
for _, hid := range t.headIds {
|
|
hd := t.attached[hid]
|
|
sId := hd.Id
|
|
if hd.Snapshot == nil {
|
|
sId = hd.LastSnapshotId
|
|
}
|
|
if slice.FindPos(sIds, sId) == -1 {
|
|
sIds = append(sIds, sId)
|
|
}
|
|
}
|
|
if len(sIds) == 1 {
|
|
return sIds[0]
|
|
} else if len(sIds) == 0 {
|
|
return ""
|
|
}
|
|
b := &stateBuilder{
|
|
cache: t.attached,
|
|
}
|
|
sId, err := b.findCommonSnapshot(ctx, sIds)
|
|
if err != nil {
|
|
log.Errorf("can't find common snapshot: %v", err)
|
|
}
|
|
return sId
|
|
}
|
|
|
|
type sortChanges []*Change
|
|
|
|
func (s sortChanges) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s sortChanges) Less(i, j int) bool {
|
|
return s[i].Id < s[j].Id
|
|
}
|
|
|
|
func (s sortChanges) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|