1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-11 18:20:33 +09:00
anytype-heart/change/state.go
2021-03-19 17:09:54 +04:00

191 lines
4.1 KiB
Go

package change
import (
"sort"
"time"
"github.com/anytypeio/go-anytype-middleware/core/block/editor/state"
)
func NewStateCache() *stateCache {
return &stateCache{
states: make(map[string]struct {
refs int
state *state.State
}),
}
}
type stateCache struct {
states map[string]struct {
refs int
state *state.State
}
}
func (sc *stateCache) Set(id string, s *state.State, refs int) {
sc.states[id] = struct {
refs int
state *state.State
}{refs: refs, state: s}
}
func (sc *stateCache) Get(id string) *state.State {
item := sc.states[id]
item.refs--
if item.refs == 0 {
delete(sc.states, id)
} else {
sc.states[id] = item
}
return item.state
}
// Simple implementation hopes for CRDT and ignores errors. No merge
func BuildStateSimpleCRDT(root *state.State, t *Tree) (s *state.State, err error) {
var (
startId string
applyRoot bool
st = time.Now()
lastChange *Change
count int
)
if startId = root.ChangeId(); startId == "" {
startId = t.RootId()
applyRoot = true
}
t.Iterate(startId, func(c *Change) (isContinue bool) {
count++
lastChange = c
if startId == c.Id {
s = root.NewState()
if applyRoot {
s.ApplyChangeIgnoreErr(c.Change.Content...)
s.SetChangeId(c.Id)
s.AddFileKeys(c.FileKeys...)
}
return true
}
ns := s.NewState()
ns.ApplyChangeIgnoreErr(c.Change.Content...)
ns.SetChangeId(c.Id)
ns.AddFileKeys(c.FileKeys...)
_, _, err = state.ApplyStateFastOne(ns)
if err != nil {
return false
}
return true
})
if err != nil {
return nil, err
}
if lastChange != nil {
s.SetLastModified(lastChange.Timestamp, lastChange.Account)
}
log.Infof("build state (crdt): changes: %d; dur: %v;", count, time.Since(st))
return s, err
}
// Full version found parallel branches and proposes to resolve conflicts
func BuildState(root *state.State, t *Tree) (s *state.State, err error) {
var (
sc = NewStateCache()
startId string
applyRoot bool
st = time.Now()
count int
)
if startId = root.ChangeId(); startId == "" {
startId = t.RootId()
applyRoot = true
}
t.IterateBranching(startId, func(c *Change, branchLevel int) (isContinue bool) {
if root.ChangeId() == c.Id {
s = root
if applyRoot {
s = s.NewState()
if err = s.ApplyChange(c.Change.Content...); err != nil {
return false
}
s.SetLastModified(c.Timestamp, c.Account)
s.AddFileKeys(c.FileKeys...)
count++
}
sc.Set(c.Id, s, len(c.Next))
return true
}
if len(c.PreviousIds) == 1 {
ps := sc.Get(c.PreviousIds[0])
s := ps.NewState()
if err = s.ApplyChange(c.Change.Content...); err != nil {
return false
}
s.SetLastModified(c.Timestamp, c.Account)
s.AddFileKeys(c.FileKeys...)
count++
s.SetChangeId(c.Id)
if branchLevel == 0 {
if _, _, err = state.ApplyStateFastOne(s); err != nil {
return false
}
sc.Set(c.Id, ps, len(c.Next))
} else {
sc.Set(c.Id, s, len(c.Next))
}
} else if len(c.PreviousIds) > 1 {
toMerge := make([]*state.State, len(c.PreviousIds))
sort.Strings(c.PreviousIds)
for i, prevId := range c.PreviousIds {
toMerge[i] = sc.Get(prevId)
}
ps := merge(t, toMerge...)
s := ps.NewState()
if err = s.ApplyChange(c.Change.Content...); err != nil {
return false
}
s.SetLastModified(c.Timestamp, c.Account)
s.AddFileKeys(c.FileKeys...)
count++
s.SetChangeId(c.Id)
if branchLevel == 0 {
if _, _, err = state.ApplyStateFastOne(s); err != nil {
return false
}
sc.Set(c.Id, ps, len(c.Next))
} else {
sc.Set(c.Id, s, len(c.Next))
}
}
return true
})
if err != nil {
return nil, err
}
if len(t.headIds) > 1 {
toMerge := make([]*state.State, len(t.headIds))
sort.Strings(t.headIds)
for i, hid := range t.headIds {
if s.ChangeId() == hid {
toMerge[i] = s
} else {
toMerge[i] = sc.Get(hid)
}
}
s = merge(t, toMerge...)
}
log.Infof("build state: changes: %d; dur: %v;", count, time.Since(st))
return
}
func merge(t *Tree, states ...*state.State) (s *state.State) {
for _, st := range states {
if s == nil {
s = st
} else {
s = s.Merge(st)
}
}
return s
}