-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Added Fallible System Params #7162
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
Added Fallible System Params #7162
Conversation
Example of resultful system param use bevy::{
ecs::system::*,
prelude::*,
};
#[derive(Debug, Resource)]
struct Foo {
foo: i32,
}
#[derive(Debug, Resource)]
struct Bar {
bar: u32,
}
#[derive(SystemParam, Debug)]
#[system_param(resultful(MyParamErr))]
struct MyParam<'w> {
#[system_param(optional)]
foo: Res<'w, Foo>,
#[system_param(optional)]
bar: Res<'w, Bar>,
}
#[derive(Debug)]
enum MyParamErr {
Foo,
Bar,
}
impl std::fmt::Display for MyParamErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let field = match self {
MyParamErr::Foo => "Res<Foo>",
MyParamErr::Bar => "Res<Bar>"
};
write!(f, "MyParam was missing {field}")
}
}
impl std::error::Error for MyParamErr {}
impl From<SystemParamError<Res<'_, Foo>>> for MyParamErr {
fn from(_: SystemParamError<Res<'_, Foo>>) -> Self {
Self::Foo
}
}
impl From<SystemParamError<Res<'_, Bar>>> for MyParamErr {
fn from(_: SystemParamError<Res<'_, Bar>>) -> Self {
Self::Bar
}
}
fn main() {
App::new()
.insert_resource(Foo { foo: 653 })
.insert_resource(Bar { bar: 534 })
.add_system(foo)
.run();
}
fn foo(foo: Result<MyParam, MyParamErr>) {
match foo {
Ok(o) => println!("{o:?}"),
Err(e) => println!("{e}"),
}
} |
Still need to clean up the derive macro and fix some bugs with it (like it still requiring an error type in the field-level attribute). |
Handy! This is uncontroversial but fairly complex IMO. Ping me if I'm slow to review this once it's ready. |
The only thing I'm still undecided about is whether Edit:Oh yeah, I forgot it had |
Yeah don't worry about verbosity here on a niche error type. Clarity is more important. |
ecf063a
to
9103751
Compare
@alice-i-cecile Okay, I'll clean up the docs. It's still not ready though: I keep on having a bug with the derive macro with long system params (more than 16 fields), where it panics (I was trying to clean it up, but I clearly messed something up in the process). I was just fixing some merge issues with the hopes it might resolve itself. |
No worries, I just saw this PR and was in the mood to help out with some docs as I tried to understand what was going on a bit better <3 |
Co-authored-by: Alice Cecile <[email protected]>
@alice-i-cecile Hey, I've added an example for optional system params, and I was hoping to get some feedback. I just wanted to make sure that my example isn't too niche or doesn't focus on the subject enough. The pattern it demonstrates is something I'm using in bevy_atmosphere that I felt could be useful for other libraries as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh cool, I really like that example. More advanced, but it's an advanced feature and it's nice to show reasonable patterns.
Can you add a note in the example module level docs explaining that this is a relatively advanced usage, and that much of the time you can just use the derive macro?
Will do, thanks! :) |
Okay, branch is now up-to-date. @maniwani Could you review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea, Docs, and Code LGTM
Thank you @Diddykonga for your review. @alice-i-cecile I think this PR is ready to be merged, unless you want to take another look at it (there were only a few minor changes to make it up-to-date and some small documentation edits to clarify how the macro works). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry it's taken me so long to review this. I agree with the motivation, but I think this PR still needs work. Here's some points in no particular order.
- I don't love the name
Resultful
-- it doesn't really make sense as a word to me, and it clashes withsystem_param(infallible)
.FallibleSystemParam
just makes more sense IMO. - I don't think the derive macro is in a very good state right now. Not to be harsh, but most of the examples kind of look like attribute soup.
- I think the best approach is to include derive macro support in a follow-up PR, where hopefully we can settle on a nice and usable design. For now, I think manual implementations are acceptable since optional and fallible params are sorta niche, and at the end of the day this feature just needs to let users bypass the orphan rule.
- This PR causes the panic messages for types like
Res<T>
to regress. Instead of including a specialized message with a suggestion for how to fix it, it just spits out a generic message for all optional system params. - I'm still struggling to imagine a case where fallible system params would be useful. Optional system params are clearly useful, both within the engine and without (your
optional_system_param.rs
example very nicely illustrates a nontrivial usecase). But for fallible system params, I can't imagine a case where you would do anything except immediately unwrap or log the error case (this is also what your example does). And at that point, why not just do the error handling inSystemParam::get_param
?- I would be a lot more comfortable with this PR if fallible system params were factored out to a different PR. Even if we do decide that they're worth adding, I don't think there's any need to bundle them together with optional system params.
@JoJoJet Thank you for taking the time to review this. In response to your points:
|
That would be better. How about we take it a step further, and combine Currently, the traits are defined like this: pub trait OptionalSystemParam { ... }
impl<T: OptionalSystemParam> SystemParam for T { ... }
pub trait ResultfulSystemParam { ... }
impl<T: ResultfulSystemParam> OptionalSystemParam for T { ... } This means that every resultful param is also an optional param, is also a regular param. I suggest we just cut out |
SystemParamStructUsage::Infallible => match field_fallibility { | ||
SystemParamFieldUsage::Infallible => quote! { #ident }, | ||
SystemParamFieldUsage::Optional => quote! { #ident.expect("Optional system param was not infallible!") }, | ||
SystemParamFieldUsage::Resultful => quote! { #ident.expect("Resultful system param was not infallible!") }, | ||
_ => unreachable!(), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly, this means that if a custom SystemParam
has a field with incorrect fallibility, it will panic at runtime instead of failing at compile time. I don't think this is an acceptable failure mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is an example of that:
#[derive(SystemParam)] // By default, it's infallible, which means it must return or panic
struct MyParam<'w> {
#[system_param(optional)]
foo: Res<'w, SomeResource>,
}
What it is doing is getting the param as Option<Res<SomeResource>>
instead of Res<SomeResource>
and unwrapping it. The only difference from not getting it as Res<SomeResource>
is that if it panics, it will panic here instead of from inside the SystemParam
implementation for Res<SomeResource>
.
Essentially, you would never want to write this, because it's pointless. However, I kept it in for consistency and for usability in macros (it makes it easier to write macros when there are less edge cases to handle). Otherwise, there aren't any downsides.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see. That's a lot better than what I thought it was doing, but I'd still prefer we just have a compiler error. If there's never any reason to write this, then I don't see any point in supporting it. If we want consistency, then there should be only one right way of doing this IMO.
match syn::parse(quote! { #field_type: #bevy_ecs::system::ReadOnlySystemParam }.into()) | ||
{ | ||
Ok(o) => o, | ||
Err(e) => { | ||
return syn::Error::new( | ||
Span::call_site(), | ||
format!("Failed to create read-only predicate: {e}"), | ||
) | ||
.into_compile_error() | ||
.into() | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can also go back to using parse_quote!
I believe.
@JoJoJet That could work (it would solve the problem of The downside would be that you would be forced to have an error type and return Otherwise, I could just change the panic message to include the system name and type. |
Yes, that's what I was picturing :). That would also cut out on a lot of boilerplate, since now there's only two near-identical traits instead of three.
True, but you could always use |
Okay, I guess I'll spin up another PR with those changes. I'll leave this one up just in case it turns out to have unexpected issues. |
Also: I think this is a good idea either way. |
Backlog cleanup: closing due to inactivity, and in favour of its successor #8196. |
Objective
Due to orphan rules, it is not possible for downstream crates to implement
SystemParam
forOption<T>
andResult<T, E>
.Solution
Create
OptionalSystemParam
andResultfulSystemParam
traits that provide blanket implementations ofSystemParam
forOption<T>
andResult<T, E>
.It goes back to the
*SystemParam
traits design from #6854, rather than the genericSystemParam<T>
in #6923, since the separate traits allows forResultfulSystemParam
to constrain the error type.The
SystemParam
derive macro has been altered to have the struct and field attributesystem_param
, which can have the value ofinfallible
,optional
,resultful(ErrorType)
(ErrorType
is only on the struct-level), orignore
(on the field-level).To handle
OptionalSystemParam
s used in aResultfulSystemParam
, there isSystemParamError<T>
which wraps the type, allowing for users to implement handlers.Future Work
With this PR, implementing a fallible system param provides blanket implementations automatically. However, there may be unknown use cases where getting a blanket implementation is undesirable. If the need arises, it would be possible to require marker traits to apply the blanket implementations. I am not including them in this PR, because I believe they add extra boilerplate that only serves to take away control from users of the system param.
Changelog
OptionalSystemParam
ResultfulSystemParam
SystemParamError