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

Associate timers with widget ids #831

Merged
merged 12 commits into from
Apr 28, 2020
Merged

Conversation

sjoshid
Copy link
Contributor

@sjoshid sjoshid commented Apr 12, 2020

Closes #494

sjoshid added 3 commits April 12, 2020 13:40
2) Aggressive traversal is achieved by storing a map of TimerToken -> WidgetId in each window.
3) On Timer event, we check the map from step 2 and recurse only if child MIGHT contain the WidgetId.
@xStrom
Copy link
Member

xStrom commented Apr 12, 2020

I haven't looked at the code closely yet, but can't we do this association without BaseState? BaseState gets replicated a lot. I'm thinking of something like sending the WidgetId in with self.window.request_timer() and then the WidgetId gets stored close to wherever the TimerToken is stored. When the timer fires, we can have a RouteTimer{WidgetId, TimerToken} internal event that gets translated into the classic Timer event when it reaches the target widget.

I'll have more time to look into this tomorrow hopefully, but just wanted to get that thought out there.

timer_token
}

pub fn remove_timer(&mut self, timer_token: &TimerToken) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After widgets are done processing timer events, should we all ask them to remove map entry?
This is not used anywhere in examples.
I think framework needs to remove it rather than forcing widgets to remove it themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After merging up, Im clearing the map of BaseState. So remove is not needed when event is handled.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 12, 2020

WidgetId gets stored close to wherever the TimerToken is stored.

That was my first attempt. TimerToken is stored in platform window. After some discussion, we decided not to change druid shell.

When the timer fires, we can have a RouteTimer{WidgetId, TimerToken} internal event that gets translated into the classic Timer event when it reaches the target widget

The objective of this was to do aggressive traversing of Timer event. We can't let the event reach every widget to find out if it is a target or not. So I let 'timer -> widget id' entries merge_up to root. And when the event is fired, we use this map along with maybe_contains to determine if we need to recurse. When maybe_contains is false, it saves us from recursing into a potentially deep subtree.

@xStrom
Copy link
Member

xStrom commented Apr 12, 2020

Yeah druid-shell shouldn't be modified, but we could probably store the HashMap<TimerToken, WidgetId> in the druid window. 🤔

As for agressive traversing, you can still do that if you just know the WidgetId at the root, because you can still use may_contain to check if a path may contain the WidgetId.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 12, 2020

Whatever you said is what this PR does.

@cmyr cmyr added the S-needs-review waits for review label Apr 12, 2020
Copy link
Member

@xStrom xStrom left a comment

Choose a reason for hiding this comment

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

It seems that the HashMap in window is nevered cleaned up. We should remove the TimerToken entry from the window HashMap at the end of the window's event generation function.

@xStrom xStrom added S-waiting-on-author waits for changes from the submitter and removed S-needs-review waits for review labels Apr 14, 2020
@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 14, 2020

You are right. I cleaned up BaseState timer but not Window. Will fix this asap.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 15, 2020

To remove timers from window, I check is_handled flag at the end of event() in window.
This has one drawback, all handlers need to call ctx.set_handled().

Is that acceptable?

@xStrom
Copy link
Member

xStrom commented Apr 15, 2020

I think it's fine to set is_handled when the timer event reaches its destination.
Something like the following:

if *widget_id != child_ctx.base_state.id {
    recurse = child_ctx.base_state.children.may_contain(widget_id);
} else {
    child_ctx.is_handled = true;
}

That can be useful for preventing further propagation, however this is not how the removal should happen in the window, because the widget who requested the timer may not exist anymore. I think the window HashMap entry should be removed every time a Timer event comes to the window, regardless if it was handled by a widget or the widget no longer exists.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 15, 2020

I think the window HashMap entry should be removed every time a Timer event comes to the window, regardless if it was handled by a widget or the widget no longer exists.

Sounds good. With that in mind, this should work at the end of event in window.

if let Event::Timer(token) = event {
    self.timers.remove(&token);
}

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 15, 2020

I spoke too early. With the change I suggested, the timer example crashes.
I will look in to more details.

@xStrom
Copy link
Member

xStrom commented Apr 15, 2020

TimerToken is reused when available, so make sure you do the removal before you extend it with the new timer requests, because that new timer request set may contain the same id that you're removing.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 19, 2020

You are absolutely right. Looks like tokens are reused only on Windows. Linux/Mac seem to work as token aren't reused there.

@xStrom
Copy link
Member

xStrom commented Apr 19, 2020

Okay this looks functional now.

There is some more work opportunity here though. Instead of the window sending Event::Timer it should create a new Event::Internal(InternalEvent::Timer{ widget_id, token }) and send that, similarly to how LifeCycle::Internal(InternalLifeCycle::RouteFocusChanged{ widget_id, widget_id }) works. Then there's no need to pass the window's HashMap reference via EventCtx and inside WidgetPod we can handle the internal event. When it finally reaches the target widget we synthesize the real Event::Timer.

Let me know if this is something you wish to continue working on. It's completely fine if you want to just get this merged and be done for now. 👍

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 19, 2020

I prefer doing this the right way rather than just merging and creating a technical debt. Besides hopefully I'll learn something more.
I will look more in to your suggestion.
Thanks for asking.

@cmyr
Copy link
Member

cmyr commented Apr 21, 2020

sorry i've been distracted, will try and take a look at this shortly!

@cmyr cmyr self-assigned this Apr 21, 2020
@cmyr
Copy link
Member

cmyr commented Apr 22, 2020

This approach is pretty good, but It isn't quite what i had in mind; as mentioned by @xStrom, I think it would be even cleaner if we used an internal RouteTimer event to deliver the timer to only the correct widget. The advantage of this method would be that widgets would no longer see timers that were not intended for them, and so they could be less careful about always stashing and checking timer tokens.

@sjoshid
Copy link
Contributor Author

sjoshid commented Apr 22, 2020

I agree. Im working on the approach you and @xStrom are talking about. But isn't

widgets would no longer see timers that were not intended for them

already working? I ask because that was THE goal I was working towards and I thought I implemented it. 🤔

@cmyr
Copy link
Member

cmyr commented Apr 23, 2020

not if a widget has children; we will recurse into a container widget with the Timer event, which is not actually for the container but for the child.

sjoshid added a commit to sjoshid/druid that referenced this pull request Apr 26, 2020
sjoshid added a commit to sjoshid/druid that referenced this pull request Apr 26, 2020
Copy link
Member

@xStrom xStrom left a comment

Choose a reason for hiding this comment

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

Looking better! Just some minor changes needed.

@@ -147,6 +147,7 @@ pub enum InternalEvent {
MouseLeave,
/// A command still in the process of being dispatched.
TargetedCommand(Target, Command),
RouteTimer(TimerToken, WidgetId),
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment here describing this variant. Something simple like Used to route timer events. would work.

recurse = child_ctx.base_state.request_timer;
Event::Timer(*id)
}
Event::Timer(token) => Event::Timer(*token),
Copy link
Member

Choose a reason for hiding this comment

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

Add recurse = false; here because if we get this then we're already a descendant of the targeted widget.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Missed this important change. :(
Thanks.

Comment on lines 229 to 230
//In some platforms, timer tokens are reused. So it is necessary to remove token from
//window's timer before adding base state's timers to it.
Copy link
Member

Choose a reason for hiding this comment

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

In On some platforms, timer tokens are reused. So it is necessary to remove the token from the window's timer map before adding base state's timers new tokens to it.

self.timers.remove(&token);
}

//If at least one widget requested timer, collect those timers from widgets and add to window's timers map.
Copy link
Member

Choose a reason for hiding this comment

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

If at least one widget requested a timer, collect those timers from widgets and add all the requested timers to window's timers map.

Copy link
Member

@xStrom xStrom left a comment

Choose a reason for hiding this comment

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

Looks good now. Thanks for doing this!

@xStrom xStrom merged commit 6d1ff52 into linebender:master Apr 28, 2020
@sjoshid sjoshid deleted the TimerToken branch April 28, 2020 17:07
@xStrom xStrom removed the S-waiting-on-author waits for changes from the submitter label May 15, 2020
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.

associate timers with widget ids
3 participants