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

Fix volume undo for unfetched data #5608

Merged
merged 10 commits into from
Aug 9, 2021

Conversation

MichaelBuessemeyer
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer commented Jul 7, 2021

This PR fixes the bug that if the user draws over buckets, that aren't fetched by the backend, an undo action of that drawing will overwrite the backend data.

The problem is tackled by adding the original backend data to the volume undo once it was successfully fetched.
Then once the actual drawing action is undone, the bucket data before the drawing action is merged together with the original backend data to not let the backend data get overwritten. Previously, the undo action would have simply taken the bucket data before the drawing action and would not have considered the backend data that was fetched by the backend.

URL of deployed dev instance (used for testing):

Steps to test:

  • Open a volume / hybrid tracing without an existing segmentation layer

  • Annotate a large area with one color / cell and save the tracing.

  • Reload the tracing an make sure that the tracing is reloaded in the 2nd mag, not the mag with the highest resolution.

  • Once the tracing is reloaded, change to another cell and draw over some of the already annotated parts in the 2nd mag.

  • Then zoom in and verify that you actually overwrote the data in the lower layer.

  • Zoom out again and undo your volume action. Now the whole area should only be covered with the previous cell drawing from before the reloading.

  • Zoom in and verify to the highest mag and verify that the whole area is also filed there.

  • Repeat the same steps on the master. In the last step, there should be holes in the annotation, as the frontend ignored the unfetched backend data of the highest mag.

Issues:


@MichaelBuessemeyer
Copy link
Contributor Author

For documentation purposes:

The idea of this fix is, that the merge with the backend data is deferred to the lastest possible point in time -> once the actual undo action is triggered.

The fix:
Once the user draws over buckets where the backend has data but that data isn't loaded by the backend, a fetch for that data is started. Additionally, a promise is created that once the data is loaded by the backend, resolves and returns the data fetched by the backend. This promise is passed to the undo action and added to the volume undo batch. Once this promise resolves, the saga that handles the undo/redo, takes the fetched backend data, compresses it, and saves this next to the corresponding bucket in the volume undo batch.
Then once the user undos a volume action,

  • for each bucket in the volume undo batch it is checked, whether it has compressed original backend data,
  • and if that's the case, this data is merged with the other saved data and then applied
  • If there is no fetched backend data, the normal compressed bucket data is simply applied.

That's all the magic 🧙

@MichaelBuessemeyer MichaelBuessemeyer changed the title [WIP] Fix volume undo for unfetched data Fix volume undo for unfetched data Jul 9, 2021
Copy link
Member

@daniel-wer daniel-wer left a comment

Choose a reason for hiding this comment

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

Please see my comment regarding the merging :)

backendBucketData.byteLength,
);
const compressedBackendData = await byteArrayToLz4Array(backendDataAsByteArray, true);
volumeUndoPart.backendData = compressedBackendData;
Copy link
Member

Choose a reason for hiding this comment

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

I'm struggling to wrap my head around all of this, but wouldn't it be possible to do the merging which is done in line 348 and line 367, instead here, directly? In my head, this would greatly simplify the applyAndGetRevertingVolumeBatch, because no special treatment would need to be implemented, there (except of maybe waiting for the promise to be resolved). But it could very well be that I'm overlooking something. Let me know if it's easier to discuss this in a call.

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer Jul 15, 2021

Choose a reason for hiding this comment

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

In my head, this would greatly simplify the applyAndGetRevertingVolumeBatch, because no special treatment would need to be implemented, there (except maybe waiting for the promise to be resolved). But it could very well be that I'm overlooking something. Let me know if it's easier to discuss this in a call.

Indeed, it is possible to do the merging once the backend data arrived. But we decided against this to defer the more intensive computation. If the backend data is merged directly once it arrives, the following will be done for each bucket, whose backend data is being fetched, when the user drew over it:
Once the backend data is fetched:

  • decompress the current "revert"-bucket data,
  • merge it with the backend data,
  • compress the result again

And if the user then undos the action:

  • decompress the bucket data
  • apply the decompressed data

The way the code works now is more complicated but should be (not measured) more efficient in normal uses cases. Because we assume that only a few brush strokes are undone by the user and therefore only a few buckets actually need to be merged with the backend data:
Once the backend data is fetched:

  • compress the backend data

And only if the undo action is triggered do the following:

  • decompress the backend data and the "normal revert"-bucket data
  • merge them
  • apply the merged data

As you can see, in the current implementation there are some steps saved in case that the volume action is not undone.

Does this explain the design decision to you and do you think this is justified?

Copy link
Member

Choose a reason for hiding this comment

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

Thanks a lot for the thorough explanation! It does make sense now that I understand the reasoning :) Could you add a comment summarizing this rationale above line 248?

Copy link
Member

@daniel-wer daniel-wer 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 taking the time to explain your solution :) Code LGTM. I'll report back after testing.

backendBucketData.byteLength,
);
const compressedBackendData = await byteArrayToLz4Array(backendDataAsByteArray, true);
volumeUndoPart.backendData = compressedBackendData;
Copy link
Member

Choose a reason for hiding this comment

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

Thanks a lot for the thorough explanation! It does make sense now that I understand the reasoning :) Could you add a comment summarizing this rationale above line 248?

@@ -91,6 +97,14 @@ type racedActionsNeededForUndoRedo = {
redo: ?RedoAction,
};

// Better version: Let the undo stack / batch know that bucket data is not yet fetched by the backend.
Copy link
Member

Choose a reason for hiding this comment

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

This comment seems a little out of place here. Also, the "Better version:" beginning can be dropped I'd say. Maybe around line 240 would be more suitable to explain this (or above the compressBucketAndAddToUndoBatch function)?

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer Jul 16, 2021

Choose a reason for hiding this comment

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

I simply removed the comment in a previous commit, as these were only notes from the call with philipp on how to solve this bug.

And I think you reviewed an old version here, as I can no longer find the comment and I am pretty sure I removed it, before assigning you as an reviewer 👀 🦢

Copy link
Member

Choose a reason for hiding this comment

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

You did, but it seems like you didn't push this change until the 16.07. which is why I saw the "old" code during my review. In any case, all good :)

Copy link
Member

@daniel-wer daniel-wer left a comment

Choose a reason for hiding this comment

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

Works like a charm, great fix! 👍

Copy link
Member

@daniel-wer daniel-wer left a comment

Choose a reason for hiding this comment

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

👍

@MichaelBuessemeyer MichaelBuessemeyer merged commit 728ed1b into master Aug 9, 2021
@philippotto philippotto deleted the fix-volume-undo-for-unfetched-data branch June 14, 2022 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Volume undo does not set to fallback layer buckets again
2 participants