Skip to content
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

Modify Snapshot Event to publish updated local changes #923

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

devleejb
Copy link
Member

@devleejb devleejb commented Nov 3, 2024

What this PR does / why we need it?

This PR updates the behavior of the Snapshot Event to ensure that it publishes the most recent data reflecting local changes. Additionally, it modifies the CodeMirror example to update the cursor position when a snapshot occurs, which addresses an issue where the cursor momentarily jumps to the top.

  • Before: Cursor is moved to unintended position when the snapshot event is published (--backend-snapshot-threshold=1)

    2024-11-03.1.49.27.mov
  • After: Cursor is not moving when the snapshot event is published (--backend-snapshot-threshold=1)

    2024-11-03.1.50.19.mov

Any background context you want to provide?

The changes made in this PR aim to resolve this issue by ensuring the local changes are reflected in the snapshot and that the cursor position is updated accordingly during the snapshot process.

What are the relevant tickets?

Fixes #922

Checklist

  • Added relevant tests or not required
  • Didn't break anything

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced collaborative editing capabilities with improved management of selections and operations.
    • Introduced a new type, YorkiePresence, to better handle user presence and selection states.
  • Bug Fixes

    • Improved logic for applying local changes and publishing snapshot events, ensuring accurate document state representation.
  • Documentation

    • Updated type definitions for better clarity and functionality.

@devleejb devleejb self-assigned this Nov 3, 2024
Copy link

coderabbitai bot commented Nov 3, 2024

Walkthrough

The changes in this pull request involve modifications to the main.ts, type.ts, and document.ts files. The updates include the introduction of new types, adjustments to document instantiation, and enhancements in the handling of collaborative editing features. Key changes focus on improving the synchronization of selections and operations during real-time updates, as well as refining the logic for applying local changes and snapshots within the Document class.

Changes

File Path Change Summary
examples/vanilla-codemirror6/src/main.ts Updated type imports and document instantiation to include YorkiePresence. Modified syncText and updateListener functions for better selection management. Changed handleOperations to use OperationInfo.
examples/vanilla-codemirror6/src/type.ts Added new type YorkiePresence with an optional selection property. Modified import statement to include TextPosStructRange.
packages/sdk/src/document/document.ts Modified applyChangePack and applySnapshot methods to improve snapshot handling and local changes application logic.
packages/sdk/test/integration/client_test.ts Enhanced integration tests for document synchronization and error handling, including increased loop count and network failure simulations.
packages/sdk/test/unit/document/document_test.ts Updated imports and added a new test case for snapshot events with multiple clients.

Assessment against linked issues

Objective Addressed Explanation
Snapshot Event should include the most up-to-date information (Issue #922)

Possibly related PRs

Suggested reviewers

  • chacha912
  • cloneot

Poem

In the land of code where rabbits play,
Changes hop in a bright new way.
With presence and selections in sync,
Our documents dance, don’t you think?
A joyful leap, a collaborative cheer,
Together we code, with friends so near! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@devleejb devleejb marked this pull request as ready for review November 4, 2024 07:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
examples/vanilla-codemirror6/src/type.ts (1)

7-9: Consider adding JSDoc documentation for the YorkiePresence type.

While the type definition is clean and well-structured, adding documentation would help explain its purpose in managing selection state during collaborative editing and snapshot events.

Consider adding documentation like this:

+/**
+ * Represents the presence information for a user in the collaborative editor.
+ * Used to track and synchronize selection states across peers,
+ * particularly during snapshot events.
+ */
 export type YorkiePresence = {
   selection?: TextPosStructRange;
 };
examples/vanilla-codemirror6/src/main.ts (1)

112-137: Consider optimizing the selection comparison logic.

While the presence synchronization implementation is solid, the selection comparison using JSON.stringify could be optimized for better performance.

Consider replacing the JSON comparison with a direct comparison:

-if (
-  JSON.stringify(selection) !==
-  JSON.stringify(presence.get('selection'))
-) {
+const currentSelection = presence.get('selection');
+if (!currentSelection ||
+    selection[0] !== currentSelection[0] ||
+    selection[1] !== currentSelection[1]) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6ab62 and 327b5c9.

📒 Files selected for processing (3)
  • examples/vanilla-codemirror6/src/main.ts (5 hunks)
  • examples/vanilla-codemirror6/src/type.ts (1 hunks)
  • packages/sdk/src/document/document.ts (1 hunks)
🔇 Additional comments (8)
examples/vanilla-codemirror6/src/type.ts (2)

1-5: LGTM! Clean import statement and type definition.

The import statement correctly includes the necessary types, and the YorkieDoc type definition maintains a clean interface for document content.


1-9: Verify the integration with snapshot-related code.

Since this type is crucial for fixing the cursor position issues during snapshots, let's verify its integration.

✅ Verification successful

Based on the search results, I can see that:

  1. The YorkiePresence type is properly integrated with snapshot-related functionality in the SDK:

    • The Snapshot protobuf message includes presence information
    • The bytesToSnapshot converter handles presence data
    • The snapshot event handling in Document class preserves presence information
  2. The type is used consistently across examples:

    • vanilla-codemirror6
    • vanilla-quill
    • react-tldraw
  3. The snapshot handling code properly maintains presence data during:

    • Snapshot creation
    • Snapshot application
    • Local/remote change reconciliation

Let me verify one final aspect about the selection field specifically:


Let me try a different search pattern to verify the selection handling:


Based on the comprehensive analysis, I can now provide the final response:

Integration with snapshot functionality is properly implemented

The YorkiePresence type with its selection?: TextPosStructRange field is correctly integrated with the snapshot system:

  • The type is properly imported and used in editor implementations (vanilla-quill, vanilla-codemirror6)
  • The SDK provides necessary conversion utilities between index ranges and position ranges (indexRangeToPosRange, posRangeToIndexRange)
  • The presence data, including selection, is handled in the snapshot serialization/deserialization process as seen in the SDK implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how YorkiePresence is used in snapshot-related code

# Search for YorkiePresence usage
echo "Checking YorkiePresence usage:"
rg -l "YorkiePresence"

# Search for snapshot-related code that might need to handle presence
echo -e "\nChecking snapshot-related code:"
rg -A 5 "snapshot|Snapshot" --type ts

Length of output: 35245


Script:

#!/bin/bash
# Check how TextPosStructRange is handled in snapshot-related code
rg -A 3 "TextPosStructRange.*snapshot" --type ts

# Check presence selection handling in snapshot tests
rg -A 5 "selection.*snapshot" --type ts

Length of output: 92


Script:

#!/bin/bash
# Check presence selection handling in tests and implementation
rg -A 5 "selection.*presence" --type ts

# Check TextPosStructRange usage in presence-related code
rg -A 3 "TextPosStructRange" --type ts

Length of output: 2732

examples/vanilla-codemirror6/src/main.ts (5)

3-3: LGTM: Import changes align with presence and selection handling requirements.

The updated imports correctly introduce the necessary types for handling presence information and selection state management.

Also applies to: 11-11, 14-14


31-31: LGTM: Document instantiation properly typed with presence information.

The addition of YorkiePresence as a type parameter enables type-safe handling of presence information, which is crucial for selection synchronization.


58-72: LGTM: Selection preservation during text sync addresses cursor jump issue.

The implementation effectively solves the cursor jumping issue by:

  1. Retrieving the current selection from presence
  2. Converting the selection range to proper indices
  3. Including the selection state in the transaction

This ensures the cursor position is maintained during snapshot events.


153-153: LGTM: Operation handling type correctly updated.

The parameter type change to OperationInfo aligns with the updated imports while maintaining the same functionality.


Line range hint 74-77: Verify snapshot behavior with local changes.

Let's verify that the snapshot event correctly preserves local changes and selection state.

✅ Verification successful

Snapshot behavior implementation verified and working correctly

The snapshot handling is properly implemented with appropriate synchronization of both text content and selection state:

  • The snapshot event handler correctly triggers syncText() to re-sync content
  • Selection state is preserved through the snapshot via:
    • Proper conversion between position ranges and index ranges
    • Selection restoration after text replacement
    • Presence updates with selection state

The implementation follows the same pattern as the verified vanilla-quill example, ensuring consistent behavior across different editor integrations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any other snapshot-related code in the codebase
# that might affect the behavior.

echo "Checking for snapshot-related code..."
rg -l "type === 'snapshot'" --type ts

echo "Checking for selection handling in snapshot context..."
rg -l "\.selection.*snapshot" --type ts

Length of output: 367


Script:

#!/bin/bash
# Let's examine the snapshot handling implementation in both files

echo "Examining snapshot handling in vanilla-codemirror6:"
rg "type === 'snapshot'" -B 2 -A 5 examples/vanilla-codemirror6/src/main.ts

echo -e "\nExamining snapshot handling in vanilla-quill:"
rg "type === 'snapshot'" -B 2 -A 5 examples/vanilla-quill/src/main.ts

echo -e "\nChecking for selection state handling around snapshots in both files:"
rg "selection" -B 2 -A 2 examples/vanilla-codemirror6/src/main.ts
echo -e "\n---"
rg "selection" -B 2 -A 2 examples/vanilla-quill/src/main.ts

Length of output: 4203

packages/sdk/src/document/document.ts (1)

1183-1202: Potential inconsistency in source field when publishing snapshot event

After applying local changes using OpSource.Local, the snapshot event is being published with source: OpSource.Remote. Since the snapshot now includes local changes, should the source be OpSource.Local to accurately reflect that the changes are from the local client?

Copy link
Contributor

@chacha912 chacha912 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I have a few comments.

Could we add test cases to verify that the current issue has been resolved? It seems there are no existing tests for snapshot events.

packages/sdk/src/document/document.ts Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
packages/sdk/test/unit/document/document_test.ts (2)

1512-1512: Avoid assignment in expression.

The assignment within the update callback can be made more explicit for better readability.

-d1.update((r) => (r.counter = new Counter(yorkie.IntType, 0)));
+d1.update((r) => {
+  r.counter = new Counter(yorkie.IntType, 0);
+});
🧰 Tools
🪛 Biome

[error] 1512-1512: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1500-1528: Enhance test documentation.

While the test case effectively verifies the snapshot event behavior, it could benefit from more detailed comments explaining the test scenario and expectations. Consider adding comments that explain:

  1. The purpose of creating 1000 counter increases
  2. Why we're testing simultaneous counter increase during snapshot
  3. The expected behavior and why 1001 is the expected value
 it('should publish snapshot event with up-to-date document', async function ({
   task,
 }) {
   type TestDoc = { counter: Counter };
+  // Test scenario:
+  // 1. Create a counter and sync between clients
+  // 2. Generate enough changes on client 1 to trigger a snapshot
+  // 3. Verify that client 2 receives the correct counter value
+  // even when making concurrent changes during snapshot
   await withTwoClientsAndDocuments<TestDoc>(async (c1, d1, c2, d2) => {
     const eventCollector = new EventCollector<number>();
🧰 Tools
🪛 Biome

[error] 1512-1512: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/sdk/src/document/document.ts (1)

Line range hint 1417-1433: Excellent implementation of snapshot handling with local changes!

The implementation correctly ensures that snapshots reflect local changes by:

  1. Applying the snapshot
  2. Removing acknowledged local changes
  3. Reapplying remaining local changes

Consider introducing a constant for the default clientSeq value for better code clarity:

+const DEFAULT_CLIENT_SEQ = -1;
+
 public applySnapshot(
   serverSeq: bigint,
   snapshotVector: VersionVector,
   snapshot?: Uint8Array,
-  clientSeq: number = -1,
+  clientSeq: number = DEFAULT_CLIENT_SEQ,
 ) {
packages/sdk/test/integration/client_test.ts (1)

855-857: Parameterize loop count based on DefaultSnapshotThreshold

Hardcoding the loop count to 1000 may cause maintenance issues if DefaultSnapshotThreshold changes again in the future. Consider retrieving the actual value of DefaultSnapshotThreshold to make the test more adaptable and maintainable.

Apply this diff to parameterize the loop count:

+          const snapshotThreshold = DefaultSnapshotThreshold;
-          for (let i = 0; i < 1000; i++) {
+          for (let i = 0; i < snapshotThreshold; i++) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 327b5c9 and c98593f.

📒 Files selected for processing (3)
  • packages/sdk/src/document/document.ts (5 hunks)
  • packages/sdk/test/integration/client_test.ts (1 hunks)
  • packages/sdk/test/unit/document/document_test.ts (2 hunks)
🧰 Additional context used
🪛 Biome
packages/sdk/test/unit/document/document_test.ts

[error] 1512-1512: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (4)
packages/sdk/test/unit/document/document_test.ts (2)

25-30: LGTM!

The import statement has been properly organized to include the necessary types and classes.


32-32: LGTM!

The import of the helper function is appropriate for the new test case.

packages/sdk/src/document/document.ts (2)

1151-1165: Well-implemented method for managing local changes!

The implementation efficiently removes local changes that have been acknowledged by the server, using an optimal approach to process changes in order and stop early when needed.


1179-1189: Correct handling of local changes in change pack application!

The changes ensure proper management of local changes by:

  1. Passing clientSeq to applySnapshot for snapshot-based updates
  2. Removing pushed local changes after applying regular changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Snapshot Event publishes outdated data not reflecting local changes
3 participants