[HN Gopher] How to build undo/redo in a multiplayer environment ...
___________________________________________________________________
How to build undo/redo in a multiplayer environment by Liveblocks
Author : moritzplassnig
Score : 149 points
Date : 2022-06-09 13:27 UTC (9 hours ago)
(HTM) web link (liveblocks.io)
(TXT) w3m dump (liveblocks.io)
| upupandup wrote:
| Is 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 wrote:
| Our 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
|
| Our most advanced Svelte example is this one :
| 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 wrote:
| love 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 wrote:
| To 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!
| jkarneges wrote:
| Fastly announced Fanout today, which is exactly this
| https://www.fastly.com/blog/unlocking-real-time-at-the-edge
| kbyatnal wrote:
| What 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 wrote:
| We 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.
| stevenfabre wrote:
| Hi HN,
|
| We'll be here today to answer any questions you may have. Hope
| you enjoy the article!
|
| Thanks, Steven
| guillaumesalles wrote:
| I worked on the implementation so don't hesitate if you have
| deeper technical questions that the article is not covering :)
| floflow wrote:
| this is great!
| munificent wrote:
| I'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.
| kupopuffs wrote:
| This is some great nugget of knowledge, thanks for sharing.
| it's too bad all my programming enthusiasm is going into a 9-5
| tmikaeld wrote:
| Nice 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 wrote:
| Yes! We're using Durable Object under the hood :) Thanks to you
| for providing such a great platform!
| tmikaeld wrote:
| Oh, I dont work at cloudflare I'm just a user, have been
| building workers for about 3 years.
| mfester wrote:
| Nice 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 wrote:
| It'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 wrote:
| Love this style of interactive posts. I recently had to solve the
| same problem for my own app and did it pretty similarly.
| stevenfabre wrote:
| Thank you!
| vinodsantharam wrote:
| maccard wrote:
| I 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 wrote:
| Thank you so much!
|
| Would love to nerd out on the implementation too at some point
| if you'd like. :)
| yodon wrote:
| As 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 wrote:
| Thank you! Can't wait to see what you build with it.
| felix-martinez wrote:
| this is amazing!!! thank you for sharing
| schneegansmarie wrote:
| Love 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 wrote:
| By "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 wrote:
| Yes, mainly text collaboration.
| Xeoncross wrote:
| For 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 wrote:
| Yes 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
|
| 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 wrote:
| > 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 wrote:
| In 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
| aranchelk wrote:
| Interesting, 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.
| guillaumesalles wrote:
| For 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 wrote:
| Thank 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.
| [deleted]
| pierrelv wrote:
| Great content. The interactive visuals are really cool!
___________________________________________________________________
(page generated 2022-06-09 23:01 UTC)