15 ms·
How to build undo/redo in a multiplayer environment by Liveblocks
- stevenfabre 4y agoHi HN, We'll be here today to answer any questions you may have. Hope you enjoy the article! Thanks, Steven
- guillaumesalles 4y agoI worked on the implementation so don't hesitate if you have deeper technical questions that the article is not covering :)
- floflow 4y ago
- felix-martinez 4y ago
- pierrelv 4y ago
- mfester 4y agoNice work! Regarding your question on how to handle undoing a command on a shape that doesn't exist anymore, is there a way we could automatically recreate the shape?
- guillaumesalles 4y agoIt's technically possible by caching only deleted shapes if any operations in the undo stack are associated with them. But is it really the UX we want every time it's happening? Let's imagine that we work together on a design file that contains hundreds of icons. I'm responsible for adjusting the color palette while you're removing the old icons that are not used anymore. If I undo my last color update on all icons, I probably don't want to recreate the icon inadvertently and override the triage you're doing! So IMO, it's more of UX question than a technical question. Is there a better default solution that we can find to improve these edge cases? My initial thought is to have a small "toast" letting the user know that the undo operation couldn't be applied because X does not exist anymore, with a link to the version history panel to allow him to reinsert X if it wants to.
- cyral 4y agoLove this style of interactive posts. I recently had to solve the same problem for my own app and did it pretty similarly.
- stevenfabre 4y agoThank you!
- maccard 4y agoI know slightly too much about the problem space to have questions about how it's implemented, but I just want to say this is one of the neatest presentations I've seen on this site. It's a great article on an interesting topic and made 100x better by the visualisations
- stevenfabre 4y agoThank you so much! Would love to nerd out on the implementation too at some point if you'd like. :)
- DylanSp 4y agoAgreed, the visualizations are really nice.
- vinodsantharam 4y ago
- tmikaeld 4y agoNice to see someone make a product out of Cloudflare Workers (and Durable Objects for sync?). Also, excellent explanation and visualizations :) Good luck with all of it
- guillaumesalles 4y agoYes! We're using Durable Object under the hood :) Thanks to you for providing such a great platform!
- tmikaeld 4y agoOh, I dont work at cloudflare I'm just a user, have been building workers for about 3 years.
- Xeoncross 4y agoFor deleted items, could you not store a memo of the object in addition to the user-local changes they did? So if you undo a change to an object that was delete by a different user, you can restore that object first to the last known state, then apply the undo? I'm not sure this makes sense for other cases besides object deletion.
- guillaumesalles 4y agoYes that's good way to implement it! But I'm still not sure this is the default behavior that we want, even it's only for deletion. I think it's more of a UX problem than an engineering problem as explained here: https://news.ycombinator.com/item?id=31682073 https://news.ycombinator.com/item?id=31682073 To take another example, for rich text editing, I don't think undoing a formatting operation on text that has been deleted at the same time should reinsert the text.
- aranchelk 4y ago> Depending on the use case, the state of features like user selection, user page selection, user zoom setting, etc. could be included in the undo/redo stack to provide a great experience. Does anyone have an idea what these use cases could be? My mental model for undo/redo is based on productivity applications and I’m at a loss; I’m genuinely curious though since this is something I’ve been implementing.
- hamtr 4y agoIn design tools, like Figma, undo sometimes just walks backwards through your selection (even if you didn't change anything). It'll even switch between pages. I'm interesting in seeing if there are other ones people can bring up
- guillaumesalles 4y agoFor rich text editing, undo should probably reset the scroll position and the cursor close the edit you're undoing. I think reseting the time position would also make sense for video editing. So basically, it's a way to get back as much as possible in the context you were at the time of the operation that is undone.
- aranchelk 4y agoThank you, this is both a good and nuanced example. It would likely be a better UX if that scrolling (etc) brought the user back to the exact context they made that edit, but in the interest of laziness and impatience I'm just using a generic focus function. It might be nice to have more exact context data available for the life of a session as a progressive enhancement, though I think it would have to be a clever encoding, else that data would be invalid in cases such as window resizes and edits made by collaborators that change position of the object being edited.
- aranchelk 4y agoInteresting, that's a really good one, thank you. Now that I'm thinking about it, when I used to use Autodesk Fusion 360 I think it did this a bit, but until now I thought it was just another bug: they had accidentally added some UI state changes to the undo stack.
- upupandup 4y agoIs there a provider that has "websockets all over the world on edge" ? Right off the bat I am not a target customer because I don't use React. Coming from Vue/Svelte
- guillaumesalles 4y agoOur code client does not depend on any front-end technology, so you can use it with Svelte or Vue. We have a few examples that use Vue and Svelte here: https://github.com/liveblocks/liveblocks/tree/main/examples https://github.com/liveblocks/liveblocks/tree/main/examples Our most advanced Svelte example is this one : https://pixelart.liveblocks.app/ https://pixelart.liveblocks.app/ If there is enough demand, we'll make an official wrapper for Vue and Svelte! If you're looking for WebSocket without all the state synchronization we provide, there are a few well-known providers like Ably or Pusher.
- upupandup 4y agolove it! thanks for the response. So the state synchro is not possible in Ably? Trying to understand what liveblocks does on top of what Ably/Pusher provides
- guillaumesalles 4y agoTo summarize the differences between Ably/Pusher and Liveblocks, Ably/Pusher use a centralized Redis to broadcast messages to channels. Liveblocks has tiny isolated servers on the edge for every room. These two different architectures create the following trade-offs: - Ably/Pusher are lower level than Liveblocks. It doesn't have any storage associated to a channel and does not solve any conflicts. - Liveblocks provides API to migrate existing app into collaborative ones via integration with state management library like Redux/Zustand. I have a POC somewhere that try integrate with Vuex, would love to release that at some point! - Ably/Pusher charges per WebSocket message sent. Liveblocks charges per WebSocket connection. Because of that, building features like cursors can become quite expensive on Pusher. - Ably/Pusher lets you connect to multiple channels with a single WebSocket connection (because they're using a centralized Redis IIRC). Liveblocks requires a single WebSocket connection per room. Pusher is good for notifications systems, Liveblocks shines when you need to build an app like Figma or Google spreadsheet. Hope it helps!
- schneegansmarie 4y agoLove the interactive visuals. Would be awesome to see how that would work with other use cases at some point. Is that something you’re planning to do?
- guillaumesalles 4y agoBy "other use cases", are you talking about apps that are not design tools like Notion or Google Spreadsheet? Or other edge cases related to undo/redo?
- schneegansmarie 4y agoYes, mainly text collaboration.
- yodon 4y agoAs someone who's built this kind of multi-player undo/redo in the past all I can say is this looks amazing and I can't wait to try it in one of my projects - Thanks for building this!
- stevenfabre 4y agoThank you! Can’t wait to see what you build with it.
- deleted 4y ago[deleted]
- munificent 4y agoI've implemented undo/redo a number of times. I agree whole-heartedly that the Command pattern is the way to do it. Undo has a reputation for being difficult, but my experience is that it's smooth sailing as long as you build it into the tool on day 1. If the app is architected around undo, it's easy, but trying to retrofit it onto an application later is always a nightmare. This is very similar to the experience of writing a networked multiplayer game. If you build a single-player game and try to bolt multiplayer on later, you're gonna have a bad time. But if you design it for multiplayer initially and treat single-player mode as essentially just a multiplayer game with only one player, it's relatively easy. I think both of these come down to the same core issue: mutating state. When playing a game, or editing a document, you are mutating some state. To support undo, you need to capture all of those mutations so that you can reverse them. To support multiplayer, you need to capture them so that they can be synchronized with the other players. It's trivially easy in most programs to just directly mutate some state by setting fields or by calling methods that do that under the hood. So, if you just start coding, you will end up with mutation happening everywhere. At that point, you have already lost. But if you design your application for undo, you isolate the document state from the rest of the application so that the only way to modify it is by going through the undo/redo mechanism. (In other words, the only way to apply a change is to create a Command object which does it on your behalf.) Likewise, if you design for multiplayer, you'll build a separation between game state and the rest of the application. Then the program has a well-defined interface that can modify the state. Once all mutation goes through a narrow well-defined interface, it's relatively easy to grow the application over time without compromising undo or multiplayer. But if you're adding that afterwards, you have to dig through the program to find every single piece of code that changes some state. It's hell.
- XCSme 4y agoFor https://curvefever.pro https://curvefever.pro we actually decided to store the state of the world for undo purposes, this way you can go anywhere back in time in constant time, and to redo you execute the commands again. The big advantage is that you don't have to create an undo function for each command (which might be tricky in some cases, as many times it still involves storing state) and you don't have to iterate through/apply all the commands to go back at a specific time. To save memory, we actually only store the state every 5 ticks (so if you want go to back to tick 23, you go back to tick 20 and run the simulation forward for 3 ticks).
- kbyatnal 4y agoWhat a fantastic, well-written blog post - well done. Out of curiosity, what's the max # of simultaneous connections per room that LiveBlocks can support? (it's hidden behind the enterprise signup flow today)
- stevenfabre 4y agoWe support 20 simultaneous connections per room on the Pro plan. With the organization plan, we’ve been able to increase this to about 50 simultaneous connections per room depending on the use case. We would technically be able to go beyond that but will likely require bigger servers. Depends on what you’re trying to build.
- pphysch 4y ago> In a multiplayer command-based undo/redo system, we can also solve [intermediary commands] by pausing and resuming the history stack at the right time. Suppose that instead of managing the overall state of the history stack, you gave each command a unique ID and allowed commands to be updated/overwritten as they develop? Apart from a few bytes of memory overhead per command, what am I missing?
- iKlsR 4y agoLight mode for the site please, those of us with Astigmatism can't use it. Supabase launched the same and they eventually had to add it because of demand, not sure why dark mode is so heavily default nowadays. https://jessicaotis.com/academia/never-use-white-text-on-a-black-background-astygmatism-and-conference-slides/ https://jessicaotis.com/academia/never-use-white-text-on-a-b...
- stevenfabre 4y agoNoted. Thanks for the feedback. We do have a light theme version on the dashboard, it would be great to add it to the website too.