ls1intum / Apollon

UML Modeling Editor written in React
https://apollon-library.readthedocs.io
MIT License
65 stars 22 forks source link

Draft: Add patch tracking #297

Closed loreanvictor closed 5 months ago

loreanvictor commented 1 year ago

Checklist

Motivation and Context

As a user of ApollonEditor, you can be notified when the state changes, but you won't be told what has changed. This is particularly problematic if the state is to be synced with some remote (e.g. realtime collaboration), as in such scenarios exact changes need to be communicated between clients to avoid race conditions (and also for communication efficiency).

Description

With this proposed change, two external methods will be added to ApollonEditor:

subscribeToModelChangePatches(callback: (patch: Patch) => void): void;
unsubscribeFromModelChangePatches(callback: (patch: Patch) => void): void;

The callback will be passed patches in format of JSON Patch (RFC 6902), which can be sent to any remote for syncing the state, in a standard compliant manner (the remote doesn't need any semantic knowledge of the model to be able to keep it in sync).

Internally, a Patcher class is added, which provides a redux middleware to intercept actions of interest and produce patches accordingly. Since this operation can be costly on large states, the Patcher can also accept Streamers for high-frequency actions (such as moving or resizing an element). These streamers act similar to reducers, except they only produce the change in state instead of the new state, bypassing the computational cost. Example streamers are implemented for moving and resizing elements.

This design is chosen to introduce the concept in a non-invasive manner, maintain simplicity of code (no need to think about "patches" when writing a new reducer), while allowing optimisation (via writing streamers) when efficiency is needed.

Caveats

Model Schema

The current design DOES NOT fully resolve race conditions. Currently, in a UMLModel, elements are stored in an array and hence indexed by an arbitrary integer, which means one client might delete element index 2 while another is adding element index 3. Since elements have a unique identifier, and since their order is completely arbitrary, I would recommend changing the schema to index elements by their actual ID. This also would speed up the client drastically as the React components basically work with a state in which elements are indexed by their ID in a map, so computational cost of constantly translating between these two schemas would also be gone.

An alternative would be to use a custom patching format and mechanism, that handles the particular semantics of UMLModel. I would strongly advise against this alternative, as this deviates from web standards and requires all remote clients, including potential server-side code that is to serve Apollon-based applications, to be particularly coupled with semantics of UMLModel, greatly reducing its flexibility.

Applying Patches

Currently there is no mechanism for applying patches. I have conducted a crude test using a heavy-handed re-import, and on that front have faced some issues with the layouter not reacting properly to imported changes. I feel it is possible that there would be need for some additional features in ApollonEditor to properly handle applying patches. These changes might need some fixes to the layouter as well (I have already commented out a problematic part of its code).

Signature for subscription methods

The subscription methods DO NOT follow the same standard as other subscription methods, as those currently are buggy (a key can be re-used for different subscriptions, so a unsubscribing from a key shared between consumers can result in the wrong listener being removed). This signature is generally non-standard, and I would highly recommend switching to a standard method for this (for example, rxjs observables would be a good idea, or even following react's own convention of returning a cleanup function on subscription, which is basically the same concept).

Steps for Testing

Create an ApollonEditor and subscribe to its changes. Logging them, vs discrete changes, would be an easy way to check the differences (I've found that this also outlines some apparent bugs in the layouter, which might require further investigation).

A more comprehensive test can be done by setting up realtime collaboration, for example on Apollon Standalone. I can provide a crude version for this if need be.

Test Coverage

No tests are currently implemented, as I suspect there will be a lot of more changes to come. Will do when the changes are further stabilised.

Screenshots

The changes are in exposed API and do not affect Apollon's interface itself. However, a client using these new APIs for smoother realtime collaboration would look like this:

ezgif com-video-to-gif