1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00
any-sync/commonspace/object/tree/objecttree/changediffer.go
2025-01-21 15:42:35 +01:00

230 lines
5.4 KiB
Go

package objecttree
import (
"go.uber.org/zap"
"golang.org/x/exp/slices"
"github.com/anyproto/any-sync/util/slice"
)
type (
hasChangesFunc func(ids ...string) bool
treeBuilderFunc func(heads []string) (ReadableObjectTree, error)
onRemoveFunc func(ids []string)
)
type ChangeDiffer struct {
hasChanges hasChangesFunc
attached map[string]*Change
waitList map[string][]*Change
visitedBuf []*Change
}
func NewChangeDiffer(tree ReadableObjectTree, hasChanges hasChangesFunc) (*ChangeDiffer, error) {
diff := &ChangeDiffer{
hasChanges: hasChanges,
attached: make(map[string]*Change),
waitList: make(map[string][]*Change),
}
if tree == nil {
return diff, nil
}
err := tree.IterateRoot(nil, func(c *Change) (isContinue bool) {
diff.add(&Change{
Id: c.Id,
PreviousIds: c.PreviousIds,
})
return true
})
if err != nil {
return nil, err
}
return diff, nil
}
func (d *ChangeDiffer) RemoveBefore(ids []string, seenHeads []string) (removed []string, notFound []string, heads []string) {
var (
attached []*Change
attachedIds []string
)
for _, id := range ids {
if ch, ok := d.attached[id]; ok {
attached = append(attached, ch)
attachedIds = append(attachedIds, id)
continue
}
// check if we have it at the bottom
if !d.hasChanges(id) {
notFound = append(notFound, id)
}
}
heads = make([]string, 0, len(seenHeads))
heads = append(heads, seenHeads...)
d.dfsPrev(attached, func(ch *Change) (isContinue bool) {
heads = append(heads, ch.Id)
heads = append(heads, ch.PreviousIds...)
removed = append(removed, ch.Id)
return true
}, func(changes []*Change) {
for _, ch := range removed {
delete(d.attached, ch)
}
for _, ch := range d.attached {
ch.Previous = slice.DiscardFromSlice(ch.Previous, func(change *Change) bool {
return change.visited
})
}
})
slices.Sort(heads)
heads = slice.RemoveRepeatedSorted(heads)
heads = append(heads, attachedIds...)
heads = append(heads, seenHeads...)
heads = slice.RemoveUniqueElementsSorted(heads)
heads = slice.DiscardDuplicatesSorted(heads)
return
}
func (d *ChangeDiffer) dfsPrev(stack []*Change, visit func(ch *Change) (isContinue bool), afterVisit func([]*Change)) {
d.visitedBuf = d.visitedBuf[:0]
defer func() {
if afterVisit != nil {
afterVisit(d.visitedBuf)
}
for _, ch := range d.visitedBuf {
ch.visited = false
}
}()
for len(stack) > 0 {
ch := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if ch.visited {
continue
}
ch.visited = true
d.visitedBuf = append(d.visitedBuf, ch)
for _, prevCh := range ch.Previous {
if !prevCh.visited {
stack = append(stack, prevCh)
}
}
if !visit(ch) {
return
}
}
}
func (d *ChangeDiffer) Add(changes ...*Change) {
for _, ch := range changes {
d.add(ch)
}
return
}
func (d *ChangeDiffer) add(change *Change) {
_, exists := d.attached[change.Id]
if exists {
return
}
d.attached[change.Id] = change
wl, exists := d.waitList[change.Id]
if exists {
for _, ch := range wl {
ch.Previous = append(ch.Previous, change)
}
delete(d.waitList, change.Id)
}
for _, id := range change.PreviousIds {
prev, exists := d.attached[id]
if exists {
change.Previous = append(change.Previous, prev)
continue
}
wl := d.waitList[id]
wl = append(wl, change)
d.waitList[id] = wl
}
}
type DiffManager struct {
differ *ChangeDiffer
readableTree ReadableObjectTree
notFound map[string]struct{}
onRemove func(ids []string)
heads []string
seenHeads []string
}
func NewDiffManager(initHeads, curHeads []string, treeBuilder treeBuilderFunc, onRemove onRemoveFunc) (*DiffManager, error) {
allHeads := make([]string, 0, len(initHeads)+len(curHeads))
allHeads = append(allHeads, initHeads...)
allHeads = append(allHeads, curHeads...)
readableTree, err := treeBuilder(allHeads)
if err != nil {
return nil, err
}
differ, err := NewChangeDiffer(readableTree, readableTree.HasChanges)
if err != nil {
return nil, err
}
return &DiffManager{
differ: differ,
heads: curHeads,
seenHeads: initHeads,
onRemove: onRemove,
readableTree: readableTree,
notFound: make(map[string]struct{}),
}, nil
}
func (d *DiffManager) Init() {
removed, _, seenHeads := d.differ.RemoveBefore(d.seenHeads, []string{})
d.seenHeads = seenHeads
d.onRemove(removed)
}
func (d *DiffManager) SeenHeads() []string {
return d.seenHeads
}
func (d *DiffManager) Remove(ids []string) {
removed, notFound, seenHeads := d.differ.RemoveBefore(ids, d.seenHeads)
for _, id := range notFound {
d.notFound[id] = struct{}{}
}
d.seenHeads = seenHeads
d.onRemove(removed)
}
func (d *DiffManager) Add(change *Change) {
d.differ.Add(change)
}
func (d *DiffManager) Update(objTree ObjectTree) {
var (
toAdd = make([]*Change, 0, objTree.Len())
toRemove []string
)
err := objTree.IterateRoot(nil, func(ch *Change) bool {
if ch.IsNew {
toAdd = append(toAdd, &Change{
Id: ch.Id,
PreviousIds: ch.PreviousIds,
})
}
if _, ok := d.notFound[ch.Id]; ok {
toRemove = append(toRemove, ch.Id)
delete(d.notFound, ch.Id)
}
return true
})
if err != nil {
log.Warn("error while iterating over object tree", zap.Error(err))
return
}
d.differ.Add(toAdd...)
d.heads = make([]string, 0, len(d.heads))
d.heads = append(d.heads, objTree.Heads()...)
removed, _, seenHeads := d.differ.RemoveBefore(toRemove, d.seenHeads)
d.seenHeads = seenHeads
d.onRemove(removed)
}