Skip to content

Commit

Permalink
Move Presence from Client to Document (#582)
Browse files Browse the repository at this point in the history
To ensure atomic delivery of data for `Document` and `Presence` to users,
we are removing `Presence` from the `Client` and moving it to the
`Document`. In the future, we plan to further modify it by changing
`Document` to `Channel` or `Room` and rename `Root` as `Document`.

UpdatePresence

`UpdatePresence API` has been removed. Now, document and presence
changes are transmitted using the `PushPull API`. Consequently, the
presence is no longer transmitted as a component of the `Client`, but
rather included within the change. ChangePack now contains `presence`.

WatchDocument

In the `WatchDocument API`, both the request and response only include
the client ID, excluding the presence.

---------

Co-authored-by: Youngteac Hong <susukang98@gmail.com>
  • Loading branch information
chacha912 and hackerwins committed Jul 18, 2023
1 parent eed0a08 commit dba636f
Show file tree
Hide file tree
Showing 62 changed files with 2,706 additions and 2,081 deletions.
2 changes: 1 addition & 1 deletion admin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func (c *Client) ListChangeSummaries(
}
var summaries []*types.ChangeSummary
for _, c := range changes {
if err := newDoc.ApplyChanges(c); err != nil {
if _, err := newDoc.ApplyChanges(c); err != nil {
return nil, err
}

Expand Down
44 changes: 16 additions & 28 deletions api/converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import (
"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/api/converter"
"github.com/yorkie-team/yorkie/api/types"
api "github.com/yorkie-team/yorkie/api/yorkie/v1"
"github.com/yorkie-team/yorkie/pkg/document"
"github.com/yorkie-team/yorkie/pkg/document/crdt"
"github.com/yorkie-team/yorkie/pkg/document/innerpresence"
"github.com/yorkie-team/yorkie/pkg/document/json"
"github.com/yorkie-team/yorkie/pkg/document/presence"
"github.com/yorkie-team/yorkie/pkg/document/time"
"github.com/yorkie-team/yorkie/test/helper"
)
Expand All @@ -41,14 +42,14 @@ func TestConverter(t *testing.T) {

doc := document.New("d1")

err = doc.Update(func(root *json.Object) error {
err = doc.Update(func(root *json.Object, p *presence.Presence) error {
root.SetNewText("k1").Edit(0, 0, "A")
return nil
})
assert.NoError(t, err)
assert.Equal(t, `{"k1":[{"val":"A"}]}`, doc.Marshal())

err = doc.Update(func(root *json.Object) error {
err = doc.Update(func(root *json.Object, p *presence.Presence) error {
root.SetNewText("k1").Edit(0, 0, "B")
return nil
})
Expand All @@ -63,10 +64,10 @@ func TestConverter(t *testing.T) {
assert.Equal(t, `{"k1":[{"val":"B"}]}`, obj.Marshal())
})

t.Run("snapshot test", func(t *testing.T) {
t.Run("root snapshot test", func(t *testing.T) {
doc := document.New("d1")

err := doc.Update(func(root *json.Object) error {
err := doc.Update(func(root *json.Object, p *presence.Presence) error {
// an object and primitive types
root.SetNewObject("k1").
SetNull("k1.0").
Expand Down Expand Up @@ -138,7 +139,7 @@ func TestConverter(t *testing.T) {
t.Run("change pack test", func(t *testing.T) {
d1 := document.New("d1")

err := d1.Update(func(root *json.Object) error {
err := d1.Update(func(root *json.Object, p *presence.Presence) error {
// an object and primitive types
root.SetNewObject("k1").
SetBool("k1.1", true).
Expand Down Expand Up @@ -209,28 +210,6 @@ func TestConverter(t *testing.T) {
assert.ErrorIs(t, err, converter.ErrCheckpointRequired)
})

t.Run("client test", func(t *testing.T) {
cli := types.Client{
ID: time.InitialActorID,
PresenceInfo: types.PresenceInfo{
Presence: types.Presence{"Name": "ClientName"},
},
}

pbCli := converter.ToClient(cli)
decodedCli, err := converter.FromClient(pbCli)
assert.NoError(t, err)
assert.Equal(t, cli.ID.Bytes(), decodedCli.ID.Bytes())
assert.Equal(t, cli.PresenceInfo, decodedCli.PresenceInfo)

pbClients := converter.ToClients([]types.Client{cli})

decodedCli, err = converter.FromClient(pbClients[0])
assert.NoError(t, err)
assert.Equal(t, cli.ID.Bytes(), decodedCli.ID.Bytes())
assert.Equal(t, cli.PresenceInfo, decodedCli.PresenceInfo)
})

t.Run("tree converting test", func(t *testing.T) {
root := helper.BuildTreeNode(&json.TreeNode{
Type: "r",
Expand Down Expand Up @@ -264,4 +243,13 @@ func TestConverter(t *testing.T) {

assert.Equal(t, tree.ToXML(), clone.ToXML())
})

t.Run("empty presence converting test", func(t *testing.T) {
change, err := innerpresence.NewChangeFromJSON(`{"ChangeType":"put","Presence":{}}`)
assert.NoError(t, err)

pbChange := converter.ToPresenceChange(change)
clone := converter.FromPresenceChange(pbChange)
assert.Equal(t, change, clone)
})
}
21 changes: 21 additions & 0 deletions api/converter/from_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,30 @@ import (

api "github.com/yorkie-team/yorkie/api/yorkie/v1"
"github.com/yorkie-team/yorkie/pkg/document/crdt"
"github.com/yorkie-team/yorkie/pkg/document/innerpresence"
"github.com/yorkie-team/yorkie/pkg/document/time"
)

// BytesToSnapshot creates a Snapshot from the given byte array.
func BytesToSnapshot(snapshot []byte) (*crdt.Object, *innerpresence.Map, error) {
if snapshot == nil {
return crdt.NewObject(crdt.NewElementRHT(), time.InitialTicket), innerpresence.NewMap(), nil
}

pbSnapshot := &api.Snapshot{}
if err := proto.Unmarshal(snapshot, pbSnapshot); err != nil {
return nil, nil, fmt.Errorf("unmarshal snapshot: %w", err)
}

obj, err := fromJSONElement(pbSnapshot.GetRoot())
if err != nil {
return nil, nil, err
}

presences := fromPresences(pbSnapshot.GetPresences())
return obj.(*crdt.Object), presences, nil
}

// BytesToObject creates an Object from the given byte array.
func BytesToObject(snapshot []byte) (*crdt.Object, error) {
if snapshot == nil {
Expand Down
98 changes: 52 additions & 46 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
api "github.com/yorkie-team/yorkie/api/yorkie/v1"
"github.com/yorkie-team/yorkie/pkg/document/change"
"github.com/yorkie-team/yorkie/pkg/document/crdt"
"github.com/yorkie-team/yorkie/pkg/document/innerpresence"
"github.com/yorkie-team/yorkie/pkg/document/key"
"github.com/yorkie-team/yorkie/pkg/document/operations"
"github.com/yorkie-team/yorkie/pkg/document/time"
Expand Down Expand Up @@ -119,27 +120,6 @@ func FromDocumentSummary(pbSummary *api.DocumentSummary) (*types.DocumentSummary
}, nil
}

// FromClient converts the given Protobuf formats to model format.
func FromClient(pbClient *api.Client) (*types.Client, error) {
id, err := time.ActorIDFromBytes(pbClient.Id)
if err != nil {
return nil, err
}

return &types.Client{
ID: id,
PresenceInfo: FromPresenceInfo(pbClient.Presence),
}, nil
}

// FromPresenceInfo converts the given Protobuf formats to model format.
func FromPresenceInfo(pbPresence *api.Presence) types.PresenceInfo {
return types.PresenceInfo{
Clock: pbPresence.Clock,
Presence: pbPresence.Data,
}
}

// FromChangePack converts the given Protobuf formats to model format.
func FromChangePack(pbPack *api.ChangePack) (*change.Pack, error) {
if pbPack == nil {
Expand Down Expand Up @@ -192,6 +172,7 @@ func FromChanges(pbChanges []*api.Change) ([]*change.Change, error) {
changeID,
pbChange.Message,
ops,
FromPresenceChange(pbChange.PresenceChange),
))
}

Expand Down Expand Up @@ -240,15 +221,13 @@ func FromEventType(pbDocEventType api.DocEventType) (types.DocEventType, error)
return types.DocumentsWatchedEvent, nil
case api.DocEventType_DOC_EVENT_TYPE_DOCUMENTS_UNWATCHED:
return types.DocumentsUnwatchedEvent, nil
case api.DocEventType_DOC_EVENT_TYPE_PRESENCE_CHANGED:
return types.PresenceChangedEvent, nil
}
return "", fmt.Errorf("%v: %w", pbDocEventType, ErrUnsupportedEventType)
}

// FromDocEvent converts the given Protobuf formats to model format.
func FromDocEvent(docEvent *api.DocEvent) (*sync.DocEvent, error) {
client, err := FromClient(docEvent.Publisher)
client, err := time.ActorIDFromBytes(docEvent.Publisher)
if err != nil {
return nil, err
}
Expand All @@ -258,32 +237,12 @@ func FromDocEvent(docEvent *api.DocEvent) (*sync.DocEvent, error) {
return nil, err
}

documentID, err := FromDocumentID(docEvent.DocumentId)
if err != nil {
return nil, err
}

return &sync.DocEvent{
Type: eventType,
Publisher: *client,
DocumentID: documentID,
Type: eventType,
Publisher: client,
}, nil
}

// FromClients converts the given Protobuf formats to model format.
func FromClients(pbClients []*api.Client) ([]*types.Client, error) {
var clients []*types.Client
for _, pbClient := range pbClients {
client, err := FromClient(pbClient)
if err != nil {
return nil, err
}
clients = append(clients, client)
}

return clients, nil
}

// FromOperations converts the given Protobuf formats to model format.
func FromOperations(pbOps []*api.Operation) ([]operations.Operation, error) {
var ops []operations.Operation
Expand Down Expand Up @@ -323,6 +282,53 @@ func FromOperations(pbOps []*api.Operation) ([]operations.Operation, error) {
return ops, nil
}

func fromPresences(pbPresences map[string]*api.Presence) *innerpresence.Map {
presences := innerpresence.NewMap()
for id, pbPresence := range pbPresences {
presences.Store(id, fromPresence(pbPresence))
}
return presences
}

func fromPresence(pbPresence *api.Presence) innerpresence.Presence {
if pbPresence == nil {
return nil
}

data := pbPresence.GetData()
if data == nil {
data = innerpresence.NewPresence()
}

return data
}

// FromPresenceChange converts the given Protobuf formats to model format.
func FromPresenceChange(pbPresenceChange *api.PresenceChange) *innerpresence.PresenceChange {
if pbPresenceChange == nil {
return nil
}

var p innerpresence.PresenceChange
switch pbPresenceChange.Type {
case api.PresenceChange_CHANGE_TYPE_PUT:
p = innerpresence.PresenceChange{
ChangeType: innerpresence.Put,
Presence: pbPresenceChange.Presence.Data,
}
if p.Presence == nil {
p.Presence = innerpresence.NewPresence()
}
case api.PresenceChange_CHANGE_TYPE_CLEAR:
p = innerpresence.PresenceChange{
ChangeType: innerpresence.Clear,
Presence: nil,
}
}

return &p
}

func fromSet(pbSet *api.Operation_Set) (*operations.Set, error) {
parentCreatedAt, err := fromTimeTicket(pbSet.ParentCreatedAt)
if err != nil {
Expand Down
21 changes: 21 additions & 0 deletions api/converter/to_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,30 @@ import (

api "github.com/yorkie-team/yorkie/api/yorkie/v1"
"github.com/yorkie-team/yorkie/pkg/document/crdt"
"github.com/yorkie-team/yorkie/pkg/document/innerpresence"
"github.com/yorkie-team/yorkie/pkg/index"
)

// SnapshotToBytes converts the given document to byte array.
func SnapshotToBytes(obj *crdt.Object, presences *innerpresence.Map) ([]byte, error) {
pbElem, err := toJSONElement(obj)
if err != nil {
return nil, err
}

pbPresences := ToPresences(presences)

bytes, err := proto.Marshal(&api.Snapshot{
Root: pbElem,
Presences: pbPresences,
})
if err != nil {
return nil, fmt.Errorf("marshal Snapshot to bytes: %w", err)
}

return bytes, nil
}

// ObjectToBytes converts the given object to byte array.
func ObjectToBytes(obj *crdt.Object) ([]byte, error) {
pbElem, err := toJSONElement(obj)
Expand Down
Loading

0 comments on commit dba636f

Please sign in to comment.