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

Add renderCallback #126

Closed
wants to merge 4 commits into from
Closed

Conversation

pbiggar
Copy link
Contributor

@pbiggar pbiggar commented Jul 31, 2019

This adds a renderCallback function, which runs on the model after the render. We use this to set the cursor (which the browser resets after updating the text of a node).

I looked at other ways of doing this, such as a custom node, and this seemed the simplest, most general, and most performant. The other way to do this would be a node that has a callback right after it updates that runs if-and-only-if it updates. That would be pretty cool but would be a lot more work, and would not necessarily be faster.

I wasn't sure about adding renderCallback to standardProgram and navigationProgram, but defaulted to yes.

Elm doesn't have this API, but this is exactly the sort of problem that Elm's view of the world causes, and it really does need to be run right after the render.

Fixes #125.

@OvermindDL1
Copy link
Owner

OvermindDL1 commented Aug 1, 2019

I looked at other ways of doing this, such as a custom node, and this seemed the simplest, most general, and most performant.

Such an event as this has actually been wanted, so this great! :-)

The other way to do this would be a node that has a callback right after it updates that runs if-and-only-if it updates.

Yeah that will be the realm of a custom node.

That would be pretty cool but would be a lot more work, and would not necessarily be faster.

It wouldn't necessarily be any slower as all it would entail would just be calling the send message callback with the event, as there is already an event being handled it will automatically be handled on the next event handling loop right after anyway.

I wasn't sure about adding renderCallback to standardProgram and navigationProgram, but defaulted to yes.

I'll perform a review of this shortly and will think it through.

Elm doesn't have this API, but this is exactly the sort of problem that Elm's view of the world causes, and it really does need to be run right after the render.

This is precisely one of the Elm annoyances that would be fixed. ^.^

Copy link
Owner

@OvermindDL1 OvermindDL1 left a comment

Choose a reason for hiding this comment

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

Hmm, yeah an event would be better than a new callback, let me think on this a short period...

@@ -117,6 +117,7 @@ let main =
model = init (); (* Since model is a set value here, we call our init function to generate that value *)
update;
view;
renderCallback = (fun _ -> ())
Copy link
Owner

Choose a reason for hiding this comment

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

Hmm, actually thinking this over, perhaps a subscription would make more sense, that way it is fed into the normal even loop processing... It would have to touch some internals, but as it is defined within tea anyway that seems fine.

@@ -177,6 +179,7 @@ let programLoop update view subscriptions initModel initCmd = function
| Some _id ->
let newVdom = [view !latestModel] in
let justRenderedVdom = Vdom.patchVNodesIntoElement callbacks parentNode !priorRenderedVdom newVdom in
let () = renderCallback !latestModel in
Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, this doesn't allow for any commands either, so a normal event might be a lot better.

@pbiggar
Copy link
Contributor Author

pbiggar commented Aug 1, 2019

The place I need to run this callback is after immediately the render which happens asynchronously and unpredictably. Subscriptions, cmds, and other events do not have this property as I understand it?

@pbiggar
Copy link
Contributor Author

pbiggar commented Aug 1, 2019

Thinking on this more, another way to handle this would be to have a type of cmd that runs post-render only. I can make that work if you give me a rough idea of how you'd like it implemented.

@OvermindDL1
Copy link
Owner

The place I need to run this callback is after immediately the render which happens asynchronously and unpredictably. Subscriptions, cmds, and other events do not have this property as I understand it?

  • Subscriptions run when whenever the internal code tells it to run (generally something from a DOM event, but from the render event it would happen in the render function).
  • Cmd's run immediately after update returns in order of definition.

Thinking on this more, another way to handle this would be to have a type of cmd that runs post-render only. I can make that work if you give me a rough idea of how you'd like it implemented.

I think I have an idea, just just a list of events to pass to the application to be applied during and after each DOM update call, this list would be managed by a subscription.

I'm working on the idea now but I ran into a typing bug with Cmd.map (in unrelated code...) that I'm needing to fix first.

@OvermindDL1
Copy link
Owner

OvermindDL1 commented Aug 1, 2019

@pbiggar I just pushed a branch at https://github.com/OvermindDL1/bucklescript-tea/tree/renderevent with an added subscription function at Tea.Ex.render_event MyMessage. It should call that message with the update loop within the same render callbacks as the DOM update callback immediately after the DOM was just update (I wonder if it's worth it to add a pre_render_event, easy to do at this point). I have it completely and entirely untested, any chance at testing it?

If it works I'll merge it in to main. :-)

EDIT: It should pretty well no-op if there is no subscription for a render_event at the time too, hopefully nothing broke there either. Everything compiles at least. ^.^;

EDIT2: On a plus side, this also adds an event system for the central processing loop, so it is easy to add more things over time now as well.

@pbiggar
Copy link
Contributor Author

pbiggar commented Aug 1, 2019

This looks pretty good.

How do I use it? Suppose I have a function myRenderFn that I want to call after specific updates, how do I call that?

Also, looking through the code am slightly confused by there being AddRenderMsg and RemoveRenderMsg?

@OvermindDL1
Copy link
Owner

How do I use it? Suppose I have a function myRenderFn that I want to call after specific updates, how do I call that?

In your subscriptions you should be able to do something like Tea.Ex.render_event RenderHappened where RenderHappened is your message and render_event is a subscription. Then in your update when you get a RenderHappened just do whatever you need to there, it is running in the same callback as the DOM update itself, Cmd's will too.

Also, looking through the code am slightly confused by there being AddRenderMsg and RemoveRenderMsg?

Purely internal system messages, ignore those. ^.^

@pbiggar
Copy link
Contributor Author

pbiggar commented Aug 2, 2019

I can't see how this would work. When the browser does a requestAnimationFrame, bs-tea runs the view and the DOM updating in the callback so that it happens before the repaint. I need to run code that also runs in that brief window before the repaint.

Unless I'm mistaken, having an event in the main update loop will not happen before the repaint. There could even be a bunch of other events lined up before I receive the RenderHappened.

Actually, I guess the name you've chosen is the hint here. I need to run code before the render is finished, and RenderHappened is too late.

Reading this again, think I'm wrong here.

@pbiggar
Copy link
Contributor Author

pbiggar commented Aug 2, 2019

OK, so as I see it, the handler synchronously calls my update function before the repaint. Am I able to update my model and add Cmds?

@OvermindDL1
Copy link
Owner

OvermindDL1 commented Aug 2, 2019

Am I able to update my model and add Cmds?

Yep! It's the full normal update loop! And as always they will run in the same render callback as well until there are no more events, upon which it will finally exit the render callback, so you will be able to do as much as you need before the screen actually repaints. :-)

For note, RenderEvent will be passed to your update 'after' the DOM updates are applied and 'before' the repaint, this way you can define flickerless updates for things like cursor position.

@pbiggar
Copy link
Contributor Author

pbiggar commented Oct 11, 2022

FYI I finally switched over to using the render_event subscription and it works great!

@pbiggar pbiggar closed this Oct 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Setting the cursor without flicker
2 participants