-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Real-time collaboration: Enable CRDT doc state map changes #72763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
59ed61c
891bc45
200b5ce
56b0141
6175b5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ import * as Y from 'yjs'; | |
| import { | ||
| CRDT_RECORD_MAP_KEY as RECORD_KEY, | ||
| LOCAL_SYNC_MANAGER_ORIGIN, | ||
| CRDT_STATE_PERSISTED_AT_KEY as PERSISTED_AT_KEY, | ||
| CRDT_STATE_PERSISTED_BY_KEY as PERSISTED_BY_KEY, | ||
| } from './config'; | ||
| import { createPersistedCRDTDoc, getPersistedCrdtDoc } from './persistence'; | ||
| import { getProviderCreators } from './providers'; | ||
|
|
@@ -305,11 +307,49 @@ export function createSyncManager(): SyncManager { | |
| return createPersistedCRDTDoc( entityState.ydoc ); | ||
| } | ||
|
|
||
| /** | ||
| * Update the last persisted timestamp, and the user who last persisted the document in the CRDT document state map. | ||
| * | ||
| * This can be used to send notifications to other peers about when the document | ||
| * was last updated, and by whom. | ||
| * | ||
| * @param {ObjectType} objectType Object type. | ||
| * @param {ObjectID} objectId Object ID. | ||
| * @param {string} origin The source of change. | ||
| */ | ||
| function recordPersistence( | ||
| objectType: ObjectType, | ||
| objectId: ObjectID, | ||
| origin: string | ||
| ): void { | ||
| const entityId = getEntityId( objectType, objectId ); | ||
| const entityState = entityStates.get( entityId ); | ||
|
|
||
| if ( ! entityState ) { | ||
| return; | ||
| } | ||
|
|
||
| const { ydoc } = entityState; | ||
|
|
||
| const lastPersistedAt = Date.now(); | ||
|
|
||
| Y.transact( | ||
| ydoc, | ||
| () => { | ||
| const stateMap = ydoc.getMap( 'state' ); | ||
| stateMap.set( PERSISTED_AT_KEY, lastPersistedAt ); | ||
| stateMap.set( PERSISTED_BY_KEY, ydoc.clientID ); | ||
|
Comment on lines
+360
to
+361
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been wondering if this is really the best approach. It's not really document state and it feels fragile.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem I guess is that, we need to be able to tell the other clients that the document has been updated and by whom without spamming notifications. If we hook into each change, we'd be spamming notifications. If we hook into when the post is saved, then those changes are batched at least. That's why I stuck with this approach.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized why I like this approach. It's like our very own redux action that we can fire that plugins can hook into to react the way they want. |
||
| }, | ||
| origin | ||
| ); | ||
| } | ||
|
|
||
| return { | ||
| createMeta: createEntityMeta, | ||
| load: loadEntity, | ||
| undoManager, | ||
| unload: unloadEntity, | ||
| update: updateCRDTDoc, | ||
| markPersisted: recordPersistence, | ||
| }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has not yet been persisted at this point, so this creates a race condition if peers use it as a signal to refetch the entity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want that ability in place? I took away the
refetch the entitycomment for that reason and made it seem instead its for notifications.I had tried to put it under actions like it used to be, but that didn't consistently get triggered which was odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other option that I had in mind actually was the action -
editor.savePost. That's fired right after the post update finishes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It served an important purpose in syncing the state of the Save / Publish button, so that peers can update their entity record in their local store and correctly compute "dirty" state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then the question is, where would that be called? In the VIP-RTC plugin maybe? So once could decide if they truly want this feature or not and how they use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be called in the sync manager. You can see how it previously worked here:
https://github.com/Automattic/gutenberg/blob/686d1b3381f29198d31af24b76c601abdc5d6da5/packages/sync/src/provider.ts#L124
Right now in our plugin branch, we've regressed to a non-synced Save / Publish button state. Refetching is how we solved that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, I meant to say that do we want that ability in this place not at all.
I definitely know why we had that in place, as we went through several PRs to figure out the right solution for it. My thought was that, we'd use this state key way just for notifications so if it is a touch fragile it won't be a big deal. But, for getting the publish/save state synced we'd need to make sure that's perfect.
I have an alternative area that I'm experimenting with, which is the editor.savePost action.
Would perhaps re-naming these two keys to something else help? Or do you think it's better to just do it all in one way similar to how we did it before?
I'm approaching separately, so notifications and features like re-fetching are done separately.