pkamenarsky / replica

A remote virtual DOM library for Haskell
BSD 3-Clause "New" or "Revised" License
140 stars 15 forks source link

Question about client-side prediction #23

Open DrewEaster opened 3 years ago

DrewEaster commented 3 years ago

Hey,

Thanks for this awesome work - it's very inspirational and I'm convinced such techniques will have a significant role to play in the future of highly productive web development.

I have a question about something written in the docs regarding client-side prediction:

"Additionally, every input element wired with an event listener keeps its value in a capped queue in the browser DOM for a given number of frames (currently 20)"

I've specifically drawn attention to the part about being wired with an event listener. I'm just trying to understand the implications of this. I was sort of assuming that the ambition here was to keep the client and server DOMs in perfect sync. But, the suggestion here is that replicating input values is an optional thing that only applies in the given circumstance (wired with an event listener).

Does this mean that any input that is not wired with an event listener would not be kept in sync with the server DOM?

Thanks!

pkamenarsky commented 3 years ago

Hey, glad you like it!

To your question: you are correct that the server and client DOMs are always kept in sync. However, if there is no event listener on a specific input field (say onChange) then there is no way for the server to receive the client side value of that input field, so there is no need to do client side prediction there (which is only useful when there is significant lag during the client DOM value -> server DOM value -> client DOM value roundtrip).

The client DOM value will still get synced to the server DOM value, if one is set (which, again, can not depend on the client side value, because there's no event listener); otherwise it will just behave as a normal input field (with a value that's "invisible" to the server).

I might very well be missing something here though, so all feedback is welcome. I don't have much time to work on replica currently, but it's still nice to collect all issues and ideas somewhere.

DrewEaster commented 3 years ago

Thanks for responding so fast - it's really appreciated!

It's the concept of "server blindness" (i.e. client-side DOM changes to input fields that are invisible to the server) that was of the most interest to me. I think this is the most sensible thing to do. I guess the only other way would be to do something pretty bold and proactively add listeners to every input on the client-side and so send events to the back-end regardless of whether the server DOM had attached a listener or not. But this is probably easier said than done and has a bunch of edge case challenges I've not considered.

I suppose where this is coming from in my mind is simply that the server blindness does mean that it's not technically speaking true that there's perfect sync between client and server - if the client has DOM changes that are invisible to the server, then the sync is not 100%.

However, I guess the most important thing is to consider what the practical use cases are for replica. I can't actually see an obvious reason why, in light of the typical use cases, why server blindness would be an issue. At the end of the day, if one's actually application logic is not interested in the value of an input then there's really no need for the server to have visibility of the changes.

pkamenarsky commented 3 years ago

At the end of the day, if one's actually application logic is not interested in the value of an input then there's really no need for the server to have visibility of the changes.

This is the culprit. Even if we were to add event listeners to every input, what difference would it make? The server would not react to those events and thus would have no access to any client side value anyway.

This is no different than the way for example React handles things - an input without an event handler would still behave like a normal input (e.g. the user can type into it), but the (client side) code would still have no access to its value, save for "impure" tricks like accessing the underlying DOM node.

Maybe it would help to look at it from another angle. It's not like there is a server-side DOM and client-side DOM which are kept in a two-way sync somehow magically. Rather, the flow is one-way and looks like this: server creates DOM -> DOM is replicated and applied to client -> client fires event -> event is sent to server -> server creates DOM (based on event) -> ... (and so on, forever). So if no input field event is ever sent to the server, a DOM based on that event can never be created.

DrewEaster commented 3 years ago

Totally makes sense, and thanks for explaining your thoughts on this in detail. It's really useful to have your take on the problem domain as it's clear you've put a lot of effort into thinking about it.

This is probably not the right place exactly to ask this, but I observed some behaviour I don't quite understand when looking at the websocket messages going back and forth.

Screenshot from 2021-05-01 19-29-11

This is from the hi-lo example, although I saw the same behaviour in other examples too. The bit I don't understand is the first message received back from the server. It seems to be just an empty diff, and it's not completely clear to me why that is. The only explanation I can think of is that the design warrants that the server always responds to any incoming event even if that event did not trigger any actual changes in the remote DOM. Is that the correct understanding? If so, for the sake of my complete understanding, could you explain the reasons why that's necessary?

One other more general observation I had is that the payload size of browser events being sent to the server is not currently optimised in any way. In a large majority of cases, the bulk of the payload is pretty superfluous. I assume this is because replica doesn't make any assumptions about the context in which it might be used, and thus it makes sense to send the entire serialised event payload?

pkamenarsky commented 3 years ago

The only explanation I can think of is that the design warrants that the server always responds to any incoming event even if that event did not trigger any actual changes in the remote DOM. Is that the correct understanding?

This is correct. The update message also serves as confirmation that the server got the last client message and to that effect also updates some state that's needed for the client side prediction (which checks whether the received DOM value is equal to a value in the stored history of a given input field and if so, doesn't touch it). It's probably possible to send fewer messages, but yeah, the main consideration here is the client side prediction.

I assume this is because replica doesn't make any assumptions about the context in which it might be used, and thus it makes sense to send the entire serialised event payload?

Also correct, and also, I didn't prioritise optimisation at all. I see no reason to not be able to extend replica with a configuration option that specifies the event fields we are interested in, or a more space-efficient diff format, or gzip compression or the like, it just hasn't been done yet. On the other hand, I suspect that due to the requirements - a stable connection - for the most obvious use cases of replica (internal tooling etc) bandwidth is probably not going to be the most pressing issue.