Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
fourjae committed Sep 20, 2023
2 parents 86608d6 + 940941a commit 9c8752f
Show file tree
Hide file tree
Showing 14 changed files with 659 additions and 193 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/base-docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Go 1.19.2
uses: actions/setup-go@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/chart-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
github_token: ${{ github.token }}

- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Get tools dependencies
run: make tools
Expand Down
7 changes: 7 additions & 0 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ func fromStyle(pbStyle *api.Operation_Style) (*operations.Style, error) {
if err != nil {
return nil, err
}
createdAtMapByActor, err := fromCreatedAtMapByActor(
pbStyle.CreatedAtMapByActor,
)
if err != nil {
return nil, err
}
executedAt, err := fromTimeTicket(pbStyle.ExecutedAt)
if err != nil {
return nil, err
Expand All @@ -448,6 +454,7 @@ func fromStyle(pbStyle *api.Operation_Style) (*operations.Style, error) {
parentCreatedAt,
from,
to,
createdAtMapByActor,
pbStyle.Attributes,
executedAt,
), nil
Expand Down
11 changes: 6 additions & 5 deletions api/converter/to_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,12 @@ func toEdit(e *operations.Edit) (*api.Operation_Edit_, error) {
func toStyle(style *operations.Style) (*api.Operation_Style_, error) {
return &api.Operation_Style_{
Style: &api.Operation_Style{
ParentCreatedAt: ToTimeTicket(style.ParentCreatedAt()),
From: toTextNodePos(style.From()),
To: toTextNodePos(style.To()),
Attributes: style.Attributes(),
ExecutedAt: ToTimeTicket(style.ExecutedAt()),
ParentCreatedAt: ToTimeTicket(style.ParentCreatedAt()),
From: toTextNodePos(style.From()),
To: toTextNodePos(style.To()),
CreatedAtMapByActor: toCreatedAtMapByActor(style.CreatedAtMapByActor()),
Attributes: style.Attributes(),
ExecutedAt: ToTimeTicket(style.ExecutedAt()),
},
}, nil
}
Expand Down
522 changes: 350 additions & 172 deletions api/yorkie/v1/resources.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions api/yorkie/v1/resources.proto
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ message Operation {
TextNodePos to = 3;
map<string, string> attributes = 4;
TimeTicket executed_at = 5;
map<string, TimeTicket> created_at_map_by_actor = 6;
}
message Increase {
TimeTicket parent_created_at = 1;
Expand Down
6 changes: 6 additions & 0 deletions pkg/document/crdt/rga_tree_split.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ func (s *RGATreeSplitNode[V]) Remove(removedAt *time.Ticket, latestCreatedAt *ti
return false
}

// canStyle checks if node is able to set style.
func (s *RGATreeSplitNode[V]) canStyle(editedAt *time.Ticket, latestCreatedAt *time.Ticket) bool {
return !s.createdAt().After(latestCreatedAt) &&
(s.removedAt == nil || editedAt.After(s.removedAt))
}

// Value returns the value of this node.
func (s *RGATreeSplitNode[V]) Value() V {
return s.value
Expand Down
38 changes: 34 additions & 4 deletions pkg/document/crdt/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,28 +250,58 @@ func (t *Text) Edit(
func (t *Text) Style(
from,
to *RGATreeSplitNodePos,
latestCreatedAtMapByActor map[string]*time.Ticket,
attributes map[string]string,
executedAt *time.Ticket,
) error {
) (map[string]*time.Ticket, error) {
// 01. Split nodes with from and to
_, toRight, err := t.rgaTreeSplit.findNodeWithSplit(to, executedAt)
if err != nil {
return err
return nil, err
}
_, fromRight, err := t.rgaTreeSplit.findNodeWithSplit(from, executedAt)
if err != nil {
return err
return nil, err
}

// 02. style nodes between from and to
nodes := t.rgaTreeSplit.findBetween(fromRight, toRight)
createdAtMapByActor := make(map[string]*time.Ticket)
var toBeStyled []*RGATreeSplitNode[*TextValue]

for _, node := range nodes {
actorIDHex := node.id.createdAt.ActorIDHex()

var latestCreatedAt *time.Ticket
if len(latestCreatedAtMapByActor) == 0 {
latestCreatedAt = time.MaxTicket
} else {
createdAt, ok := latestCreatedAtMapByActor[actorIDHex]
if ok {
latestCreatedAt = createdAt
} else {
latestCreatedAt = time.InitialTicket
}
}

if node.canStyle(executedAt, latestCreatedAt) {
latestCreatedAt = createdAtMapByActor[actorIDHex]
createdAt := node.id.createdAt
if latestCreatedAt == nil || createdAt.After(latestCreatedAt) {
createdAtMapByActor[actorIDHex] = createdAt
}
toBeStyled = append(toBeStyled, node)
}
}

for _, node := range toBeStyled {
val := node.value
for key, value := range attributes {
val.attrs.Set(key, value, executedAt)
}
}
return nil

return createdAtMapByActor, nil
}

// Nodes returns the internal nodes of this Text.
Expand Down
2 changes: 1 addition & 1 deletion pkg/document/crdt/text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestText(t *testing.T) {
assert.Equal(t, `[{"val":"Hello "},{"val":"Yorkie"}]`, text.Marshal())

fromPos, toPos, _ = text.CreateRange(0, 1)
err = text.Style(fromPos, toPos, map[string]string{"b": "1"}, ctx.IssueTimeTicket())
_, err = text.Style(fromPos, toPos, nil, map[string]string{"b": "1"}, ctx.IssueTimeTicket())
assert.NoError(t, err)
assert.Equal(
t,
Expand Down
7 changes: 5 additions & 2 deletions pkg/document/json/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,22 @@ func (p *Text) Style(from, to int, attributes map[string]string) *Text {
}

ticket := p.context.IssueTimeTicket()
if err := p.Text.Style(
maxCreationMapByActor, err := p.Text.Style(
fromPos,
toPos,
nil,
attributes,
ticket,
); err != nil {
)
if err != nil {
panic(err)
}

p.context.Push(operations.NewStyle(
p.CreatedAt(),
fromPos,
toPos,
maxCreationMapByActor,
attributes,
ticket,
))
Expand Down
25 changes: 19 additions & 6 deletions pkg/document/operations/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type Style struct {
// to is the end point of the range to apply the style to.
to *crdt.RGATreeSplitNodePos

// latestCreatedAtMapByActor is a map that stores the latest creation time
// by actor for the nodes included in the range to apply the style to.
latestCreatedAtMapByActor map[string]*time.Ticket

// attributes represents the text style.
attributes map[string]string

Expand All @@ -44,15 +48,17 @@ func NewStyle(
parentCreatedAt *time.Ticket,
from *crdt.RGATreeSplitNodePos,
to *crdt.RGATreeSplitNodePos,
latestCreatedAtMapByActor map[string]*time.Ticket,
attributes map[string]string,
executedAt *time.Ticket,
) *Style {
return &Style{
parentCreatedAt: parentCreatedAt,
from: from,
to: to,
attributes: attributes,
executedAt: executedAt,
parentCreatedAt: parentCreatedAt,
from: from,
to: to,
latestCreatedAtMapByActor: latestCreatedAtMapByActor,
attributes: attributes,
executedAt: executedAt,
}
}

Expand All @@ -64,7 +70,8 @@ func (e *Style) Execute(root *crdt.Root) error {
return ErrNotApplicableDataType
}

return obj.Style(e.from, e.to, e.attributes, e.executedAt)
_, err := obj.Style(e.from, e.to, e.latestCreatedAtMapByActor, e.attributes, e.executedAt)
return err
}

// From returns the start point of the editing range.
Expand Down Expand Up @@ -96,3 +103,9 @@ func (e *Style) ParentCreatedAt() *time.Ticket {
func (e *Style) Attributes() map[string]string {
return e.attributes
}

// CreatedAtMapByActor returns the map that stores the latest creation time
// by actor for the nodes included in the range to apply the style to.
func (e *Style) CreatedAtMapByActor() map[string]*time.Ticket {
return e.latestCreatedAtMapByActor
}
149 changes: 149 additions & 0 deletions test/bench/document_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,155 @@ func BenchmarkDocument(b *testing.B) {
b.Run("object 10000", func(b *testing.B) {
benchmarkObject(10000, b)
})

b.Run("tree 100", func(b *testing.B) {
benchmarkTree(100, b)
})

b.Run("tree 1000", func(b *testing.B) {
benchmarkTree(1000, b)
})

b.Run("tree 10000", func(b *testing.B) {
benchmarkTree(10000, b)
})

b.Run("tree delete all 1000", func(b *testing.B) {
benchmarkTreeDeleteAll(1000, b)
})

b.Run("tree edit gc 100", func(b *testing.B) {
benchmarkTreeEditGC(100, b)
})

b.Run("tree edit gc 1000", func(b *testing.B) {
benchmarkTreeEditGC(1000, b)
})

b.Run("tree split gc 100", func(b *testing.B) {
benchmarkTreeSplitGC(100, b)
})

b.Run("tree split gc 1000", func(b *testing.B) {
benchmarkTreeSplitGC(1000, b)
})

}

func benchmarkTree(cnt int, b *testing.B) {
for i := 0; i < b.N; i++ {
doc := document.New("d1")

err := doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.SetNewTree("t", &json.TreeNode{
Type: "root",
Children: []json.TreeNode{{
Type: "p",
Children: []json.TreeNode{},
}},
})
for c := 1; c <= cnt; c++ {
tree.Edit(c, c, &json.TreeNode{Type: "text", Value: "a"})
}
return nil
})
assert.NoError(b, err)
}
}

func benchmarkTreeDeleteAll(cnt int, b *testing.B) {
for i := 0; i < b.N; i++ {
doc := document.New("d1")

err := doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.SetNewTree("t", &json.TreeNode{
Type: "root",
Children: []json.TreeNode{{
Type: "p",
Children: []json.TreeNode{},
}},
})
for c := 1; c <= cnt; c++ {
tree.Edit(c, c, &json.TreeNode{Type: "text", Value: "a"})
}
return nil
})

err = doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.GetTree("t")
tree.Edit(1, cnt+1)

return nil
})
assert.NoError(b, err)
}
}

func benchmarkTreeEditGC(cnt int, b *testing.B) {
for i := 0; i < b.N; i++ {
doc := document.New("d1")

err := doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.SetNewTree("t", &json.TreeNode{
Type: "root",
Children: []json.TreeNode{{
Type: "p",
Children: []json.TreeNode{},
}},
})
for c := 1; c <= cnt; c++ {
tree.Edit(c, c, &json.TreeNode{Type: "text", Value: "a"})
}
return nil
})

err = doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.GetTree("t")
for c := 1; c <= cnt; c++ {
tree.Edit(c, c+1, &json.TreeNode{Type: "text", Value: "b"})
}

return nil
})
assert.NoError(b, err)
assert.Equal(b, cnt, doc.GarbageLen())
assert.Equal(b, cnt, doc.GarbageCollect(time.MaxTicket))
}
}

func benchmarkTreeSplitGC(cnt int, b *testing.B) {
for i := 0; i < b.N; i++ {
doc := document.New("d1")

var builder strings.Builder
for i := 0; i < cnt; i++ {
builder.WriteString("a")
}
err := doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.SetNewTree("t", &json.TreeNode{
Type: "root",
Children: []json.TreeNode{{
Type: "p",
Children: []json.TreeNode{},
}},
})
tree.Edit(1, 1, &json.TreeNode{Type: "text", Value: builder.String()})

return nil
})

err = doc.Update(func(root *json.Object, p *presence.Presence) error {
tree := root.GetTree("t")
for c := 1; c <= cnt; c++ {
tree.Edit(c, c+1, &json.TreeNode{Type: "text", Value: "b"})
}

return nil
})
assert.NoError(b, err)
assert.Equal(b, cnt, doc.GarbageLen())
assert.Equal(b, cnt, doc.GarbageCollect(time.MaxTicket))
}
}

func benchmarkText(cnt int, b *testing.B) {
Expand Down
Loading

0 comments on commit 9c8752f

Please sign in to comment.