Skip to content

Owned callbacks. #20041

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Owned callbacks. #20041

wants to merge 1 commit into from

Conversation

viridia
Copy link
Contributor

@viridia viridia commented Jul 8, 2025

Defines a mechanism for registering callbacks which automatically unregisters the system when the parent entity is despawned.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events A-UI Graphical user interfaces, styles, layouts, and widgets X-Controversial There is active debate or serious implications around merging this PR S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through and removed X-Controversial There is active debate or serious implications around merging this PR labels Jul 8, 2025
Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

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

Could this support an API where the component boxes the system (or something like that) and then automatically registers it with an on_insert hook?

@viridia
Copy link
Contributor Author

viridia commented Jul 9, 2025

Could this support an API where the component boxes the system (or something like that) and then automatically registers it with an on_insert hook?

There's been some discussion on this: basically adding a different variant of Callback which would allow you to specify the system closure as an inline literal, avoiding the preregistration requirement. This is discussed in more detail in #19847 - unfortunately this is a tricky problem to solve.

I think we need both types of callbacks: ones that can be passed around from template to template as parameters, and ones that can be defined at the point of use.

Comment on lines +163 to +164
self.commands()
.spawn((OwnedCallbackSystem(system_id), crate::owner::OwnedBy(owner)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Registered systems are entities themselves, so you might be able to skip having an extra entity by doing something like:

Suggested change
self.commands()
.spawn((OwnedCallbackSystem(system_id), crate::owner::OwnedBy(owner)));
self.commands()
.entity(system_id.entity())
.insert(crate::owner::OwnedBy(owner));

I haven't thought through all the consequences of that, but thought it might be worth mentioning!

Copy link
Member

Choose a reason for hiding this comment

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

I much prefer that design.

@alice-i-cecile
Copy link
Member

I'm not convinced this is the right direction. If we're going to use one-shot systems, we should be leaning on the cached, persistent nature of them for deduplication across multiple buttons that do the same or related things.

If we're worried about cleanup, the simpler solution is to just store a closure on the widgets.

@viridia
Copy link
Contributor Author

viridia commented Jul 10, 2025

If we're worried about cleanup, the simpler solution is to just store a closure on the widgets.

A simple closure isn't going to get you dependency injection, and this is critical.

One of the pain points I discovered working with Quill is dealing with closure captures: having to constantly clone stuff and make dummy variables to satisfy the borrow checker. Once we introduced one-shot systems, I hit on a different strategy: replacing captured values with dependency-injected values. One substitutes for the other.

For example, say we have a widget with a callback, such that the callback wants to know certain things about the widget - like whether it's checked, focused, disabled, and so on. We can do this in a couple of ways:

  • Make the callback exclusive and pass it an EntityWorldMut. However, working with exclusive access isn't the best DX.
  • Convert all of these input sources into signals, which can be captured by the closure. Unfortunately, polling the signals still requires exclusive access.
  • Use dependency injection. We still capture a few things, like entity ids, but since these are copy/clone capturing is easy. Then inside the closure, we re-derive from the injected params all of the things that we would have captured.

Using cached systems could work, if we could come up with a solution to the type-erasure problem. That's what #19847 is about - introducing some kind of dyn trait whose impl is able to internally know the concrete type of the callback and which can be shared between multiple call sites.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events A-UI Graphical user interfaces, styles, layouts, and widgets S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants