-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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 &mut World
as a system param and make .exclusive_system()
optional
#4166
Conversation
.exclusive_system()
.exclusive_system()
and ExclusiveSystem*
.exclusive_system()
and ExclusiveSystem*
.exclusive_system()
and ExclusiveSystem*
types
Pulling in @Ratysz as this split was an intentional design decision. |
Some historical context: #1144 (comment) |
For context, this change is part of the groundwork for stageless: it seemed nicely separable, so I encouraged @maniwani to submit it seperately for ease of review.
As far as I can tell, this was the primary argument against it. Managing that complexity is where a large amount of the effort behind bevyengine/rfcs#45 was focused: we take on more complexity with respect to managing the ordering of exclusive systems (and command flushing) in order to dramatically reduce complexity elsewhere in the scheduling internals / API / mental model. Having experimented with (and helped users with) the exclusive system API since that thread, I pretty firmly agree with your initial positions from that thread:
I would also add:
|
I haven't ever stopped believing that exclusive systems should be "normal systems" 😄 |
As I've said in #1144 (comment):
This is why had the split conceptually. Every exclusive system is potentially a hard sync point - the "side effects" stageless idea was supposed to codify which exclusive systems exactly are sync points, and which can be scheduled along with parallel systems. If we can we can do away with the implementation split and the user-facing API split while maintaining this conceptual split (because it's inescepable for actually scheduling things correctly), I'm all for it. The implementation we have is a compromise rather than a deliberate decision - if it were up to me exclusive systems would've been stages instead; ironically, we'll effectively have that with stageless, it'll just be neatly swept under the rug and hidden behind a flatter API. |
What is "inescapable" and how exactly would we fail to schedule these systems correctly? The "dependency debacle" you described back in #1021 (comment) could not happen.
This is desirable IMO, and there's nothing convoluted about it, because:
It can sandwich them and it won't fail. All we had to do was add a mock component and define "total write access" for both the schedule builder and executor to see the conflict properly. By themselves, all those systems want conflicting access to component
We don't even need to say anything about one system being exclusive, because it's irrelevant. There is ambiguous read-write order and that is the "bug". So from my perspective, there is nothing about exclusive systems worthy of a formal dichotomy and this is purely a clash of aesthetic preferences. And to that, I don't think that "systems must use commands when they don't have exclusive access to the entire world" is convoluted. Our storage model should just be documented with pretty pictures depicting why we can't instantly spawn entities and insert components without moving data. |
For context, the discussion in #1021 occured before we had any formal notion of "system order ambiguities", much less tools to deal with them. I read the linked comment as an initial exploration of the idea (because it's an exteremely obvious case of it). I agree with @maniwani here: there's nothing fundamentally more challenging about scheduling / ordering exclusive systems or command flushes: they're just guaranteed to be ambiguous with (almost) literally every other system. |
I am talking about the actual implementation, again. When a systems' graph is scheduled, exclusive systems must necessarily be placed at exact spots wrt blocks of specific parallelizable systems, otherwise we massively lose perf because we'd have to effectively rebuild the graph every dispatch. As long as this is understood, I see no issue with the arguments you give. Again, the API could be whatever we want. This is not about the API. |
Right, because otherwise we're spinning our wheels constantly checking a condition (can this exclusive system run) that will never be met. I see a few options here on the implementation side:
IMO 1 is probably too strict and frustrating, especially before stageless. 2 is, in theory, in keeping with the spirit of deliberate ambiguities, but very chaotic. Both 3 and 4 have the nice property of clustering exclusive systems together automatically, which should be good for performance. We could formalize this notion with a EDIT: With these tools, I would actually be okay with 1. |
I was talking about the actual implementation. You can see where I've updated the system params to add the proper component access. I just haven't ripped out the special-casing in Also, I didn't understand what you were saying here.
If you meant "now we have to tell users to be careful about where they put exclusive systems or they risk needlessly throwing away parallelism", then yeah, but there's no need for us to rebuild the graph. (edit: I see, you meant registering new archetypes and updating the archetype component access bitsets.) Systems with
IMO, 1 is the only acceptable option. We can reduce the noise and jargon in the ambiguity warnings separately. |
No, that's not it. Exclusive systems can change archetypes, which invalidates parallel systems' cached scheduling data, requiring a rebuild. Moving a parallel system between blocks separated by hard sync points does the same, because said cached data is a bunch of tight and bespoke bitsets build for and around that specific block. It's needed to boil down the absolutely unavoidable "can this run" checks to a single bitset comparison. |
Right, I guess we can't quite run exclusive systems using the executor yet. I guess this PR can just be about removing the internal split everywhere except |
That's not how the systems executor works. |
Okay, after discussing on Discord, I will keep this PR limited to what it says on the tin, getting rid of |
would this supercede #3946? |
Yes. |
Still need to update tests/imports in the other crates, but |
bors try |
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.
Didn't review the schedule parts very closely, but the rest look good to me.
4da908f
to
25a5735
Compare
This push changed the Now it's just an sequence of |
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.
Code definitely feels better with the recent changes.
1f8a627
to
399a4a0
Compare
&mut World
as a system param and deprecate .exclusive_system()
&mut World
as a system param and make .exclusive_system()
optional
crates/bevy_ecs/macros/src/lib.rs
Outdated
@@ -254,6 +254,30 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { | |||
fn apply(&mut self, world: &mut World) { | |||
self.0.apply(world) | |||
} | |||
|
|||
fn world_access_level() -> WorldAccessLevel { | |||
let mut exclusive = false; |
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.
Nit: I feel like this could be done more reliably / clearly via an Ord
impl and a reduce.
I've already done one pass over this and I'm on board for the general direction / think its a necessary step for stageless. Not fully caught up yet on the changes, but provided the "access modeling" issue we discussed earlier has been resolved, I'm guessing we can get this merged in relatively short order. |
@@ -58,12 +58,12 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState { | |||
unsafe fn get_param( | |||
state: &'s mut Self, | |||
_: &crate::system::SystemMeta, | |||
world: &'w World, | |||
world: MaybeUnsafeCell<'w, World>, |
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.
I'm generally on board for the approach in this PR, but having thought about this problem a bit, I think all of this MaybeUnsafeCell
and world_access_level
validation stuff is a sign that we're doing this inside-out. If we can never safely call an exclusive system with a &World
reference, we should probably try to just represent that statically.
To that end, I have put together an alternative PR that adds a new ExclusiveFunctionSystem
, which implements System
(backed by ExclusiveSystemParam
impls and the assumption that &mut World
is always passed in).
This means that we can leave all of the existing "shared access system function" stuff untouched, and we can piggyback on the existing System::run
and System::run_unsafe
functions to ensure that exclusive systems are only run with System::run
(by panicking on System::run_unsafe
calls).
This impl is smaller, less invasive, and introduces fewer safety concerns. It comes at the cost of having a hard line between functions with SystemParams and ExclusiveSystemParams, but the impl in this PR already had that constraint. It also means that we have another set of ExclusiveSystemParam tuple impls, but I'm not noticing any difference in compile times.
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.
Cool to see an alternative identified, but we should be clear on the pros and cons.
this
MaybeUnsafeCell
andworld_access_level
validation stuff is a sign that we're doing this inside-out
MaybeUnsafeCell
is a workaround to &T -> &UnsafeCell<T>
not being a valid cast. @BoxyUwU suggested that may change, so I'm hoping it's only temporary.
But the general idea is not inside-out. Having to prove soundness ourselves is the nature of our access model. This PR tries to complete this model so it can express any sound access, and world_access_level
is the natural extension to fill that gap.
#6083 trades completeness (maybe "consistent API" is better wording) for the static guarantees that users can't write functions with unsound access on this level + no anxiety about us having missed some UB edge case.
fewer safety concerns
It's a little unfair to say that what's implemented here is less safe. While true, the contract of SystemParamState
is that params declare their access truthfully. UB can only result from violating that contract. That's still true.
And while not statically prevented, unsound World
access here panics at system construction, which is still earlier than unsound query access.
it comes at the cost of having a hard line between functions with SystemParams and ExclusiveSystemParams the impl in this PR already had that constraint
This PR does not have a hard line exclusive/parallel trait split, which is what causes arbitrary limits / inconsistencies. E.g. #6083 would prevent a user from implementing a custom param with a &mut World
field and from using &mut World
in a ParamSet
.
smaller, less invasive
I'll give you that. Your PR does save custom param implementers from having to change anything.
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.
But the general idea is not inside-out. Having to prove soundness ourselves is the nature of our access model. This PR tries to complete this model so it can express any sound access, and world_access_level is the natural extension to fill that gap.
Within this context, I think it is. SystemParams exist to prove to the compiler that extracting disjoint components / resources from &World
is sound. This is impossible to do in safe Rust.
MaybeUnsafeCell
and world_access_level
exist to ensure that &mut World
is never used alongside &World
derived references and that &World
is never casted to &mut World
. This is possible to do in safe Rust (and is kind of the selling feature of it).
These are different problem spaces solving different problems. I do think that choosing to merge them under one API is valid, but I think in this case doing that isn't worth the complexity / risk.
It's a little unfair to say that what's implemented here is less safe. While true, the contract of SystemParamState is that params declare their access truthfully. UB can only result from violating that contract. That's still true.
I don't think this is unfair. It expands the scope of the safety contract, making it harder to implement and verify. I think calling that "less safe" is a very fair assessment.
And while not statically prevented, unsound World access here panics at system construction, which is still earlier than unsound query access.
Only if as a SystemParam implementer you implement world_access_level
correctly / only extract &mut World
from MaybeUnsafeCell
under the right circumstances.
This PR does not have a hard line exclusive/parallel trait split, which is what causes arbitrary limits / inconsistencies. E.g. #6083 would prevent a user from implementing a custom param with a &mut World field and from using &mut World in a ParamSet.
Yup I agree that putting &mut World
in a ParamSet
would be impossible in #6083, but I think thats actually desirable (as called out in that pr).
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.
It expands the scope of the safety contract, making it harder to implement and verify.
Only if as a
SystemParam
implementer you implementworld_access_level
correctly / only extract&mut World
fromMaybeUnsafeCell
under the right circumstances.
The same could be said for ReadOnlySystemParamFetch
and SystemParamState
. I'm happy to accept this as an argument about aesthetics (i.e. "MaybeUnsafeCell
is icky" and "let's not make the contract longer"), but I don't really buy the safety angle here. Ultimately, our model has to assume honesty and correctness. We can't verify soundness if one of those contracts is violated anyway.
2e1e7f1
to
4f0b01b
Compare
- add `MaybeUnsafeCell` (in the future `&T -> &UnsafeCell<T>` may be OK) - add `WorldAccessLevel` - add concept of "all exclusive access" to `Access` - update system_param.rs - update system.rs - update function_system.rs - update system_chaining.rs - remove ExclusiveSystem* internals - add tests - fix stage tests - in ambiguity_detection, the else branch in find_ambiguities is no longer taken because exclusive systems have a component access now. And obv two empty systems will always be compatible. - update fixed_timestep.rs - update extract_param.rs Co-Authored-By: Mike <mike.hsu@gmail.com>
767730e
to
adb22e3
Compare
…arams (#6083) # Objective The [Stageless RFC](bevyengine/rfcs#45) involves allowing exclusive systems to be referenced and ordered relative to parallel systems. We've agreed that unifying systems under `System` is the right move. This is an alternative to #4166 (see rationale in the comments I left there). Note that this builds on the learnings established there (and borrows some patterns). ## Solution This unifies parallel and exclusive systems under the shared `System` trait, removing the old `ExclusiveSystem` trait / impls. This is accomplished by adding a new `ExclusiveFunctionSystem` impl similar to `FunctionSystem`. It is backed by `ExclusiveSystemParam`, which is similar to `SystemParam`. There is a new flattened out SystemContainer api (which cuts out a lot of trait and type complexity). This means you can remove all cases of `exclusive_system()`: ```rust // before commands.add_system(some_system.exclusive_system()); // after commands.add_system(some_system); ``` I've also implemented `ExclusiveSystemParam` for `&mut QueryState` and `&mut SystemState`, which makes this possible in exclusive systems: ```rust fn some_exclusive_system( world: &mut World, transforms: &mut QueryState<&Transform>, state: &mut SystemState<(Res<Time>, Query<&Player>)>, ) { for transform in transforms.iter(world) { println!("{transform:?}"); } let (time, players) = state.get(world); for player in players.iter() { println!("{player:?}"); } } ``` Note that "exclusive function systems" assume `&mut World` is present (and the first param). I think this is a fair assumption, given that the presence of `&mut World` is what defines the need for an exclusive system. I added some targeted SystemParam `static` constraints, which removed the need for this: ``` rust fn some_exclusive_system(state: &mut SystemState<(Res<'static, Time>, Query<&'static Player>)>) {} ``` ## Related - #2923 - #3001 - #3946 ## Changelog - `ExclusiveSystem` trait (and implementations) has been removed in favor of sharing the `System` trait. - `ExclusiveFunctionSystem` and `ExclusiveSystemParam` were added, enabling flexible exclusive function systems - `&mut SystemState` and `&mut QueryState` now implement `ExclusiveSystemParam` - Exclusive and parallel System configuration is now done via a unified `SystemDescriptor`, `IntoSystemDescriptor`, and `SystemContainer` api. ## Migration Guide Calling `.exclusive_system()` is no longer required (or supported) for converting exclusive system functions to exclusive systems: ```rust // Old (0.8) app.add_system(some_exclusive_system.exclusive_system()); // New (0.9) app.add_system(some_exclusive_system); ``` Converting "normal" parallel systems to exclusive systems is done by calling the exclusive ordering apis: ```rust // Old (0.8) app.add_system(some_system.exclusive_system().at_end()); // New (0.9) app.add_system(some_system.at_end()); ``` Query state in exclusive systems can now be cached via ExclusiveSystemParams, which should be preferred for clarity and performance reasons: ```rust // Old (0.8) fn some_system(world: &mut World) { let mut transforms = world.query::<&Transform>(); for transform in transforms.iter(world) { } } // New (0.9) fn some_system(world: &mut World, transforms: &mut QueryState<&Transform>) { for transform in transforms.iter(world) { } } ```
Closing since issue was resolved by #6083. |
…arams (bevyengine#6083) # Objective The [Stageless RFC](bevyengine/rfcs#45) involves allowing exclusive systems to be referenced and ordered relative to parallel systems. We've agreed that unifying systems under `System` is the right move. This is an alternative to bevyengine#4166 (see rationale in the comments I left there). Note that this builds on the learnings established there (and borrows some patterns). ## Solution This unifies parallel and exclusive systems under the shared `System` trait, removing the old `ExclusiveSystem` trait / impls. This is accomplished by adding a new `ExclusiveFunctionSystem` impl similar to `FunctionSystem`. It is backed by `ExclusiveSystemParam`, which is similar to `SystemParam`. There is a new flattened out SystemContainer api (which cuts out a lot of trait and type complexity). This means you can remove all cases of `exclusive_system()`: ```rust // before commands.add_system(some_system.exclusive_system()); // after commands.add_system(some_system); ``` I've also implemented `ExclusiveSystemParam` for `&mut QueryState` and `&mut SystemState`, which makes this possible in exclusive systems: ```rust fn some_exclusive_system( world: &mut World, transforms: &mut QueryState<&Transform>, state: &mut SystemState<(Res<Time>, Query<&Player>)>, ) { for transform in transforms.iter(world) { println!("{transform:?}"); } let (time, players) = state.get(world); for player in players.iter() { println!("{player:?}"); } } ``` Note that "exclusive function systems" assume `&mut World` is present (and the first param). I think this is a fair assumption, given that the presence of `&mut World` is what defines the need for an exclusive system. I added some targeted SystemParam `static` constraints, which removed the need for this: ``` rust fn some_exclusive_system(state: &mut SystemState<(Res<'static, Time>, Query<&'static Player>)>) {} ``` ## Related - bevyengine#2923 - bevyengine#3001 - bevyengine#3946 ## Changelog - `ExclusiveSystem` trait (and implementations) has been removed in favor of sharing the `System` trait. - `ExclusiveFunctionSystem` and `ExclusiveSystemParam` were added, enabling flexible exclusive function systems - `&mut SystemState` and `&mut QueryState` now implement `ExclusiveSystemParam` - Exclusive and parallel System configuration is now done via a unified `SystemDescriptor`, `IntoSystemDescriptor`, and `SystemContainer` api. ## Migration Guide Calling `.exclusive_system()` is no longer required (or supported) for converting exclusive system functions to exclusive systems: ```rust // Old (0.8) app.add_system(some_exclusive_system.exclusive_system()); // New (0.9) app.add_system(some_exclusive_system); ``` Converting "normal" parallel systems to exclusive systems is done by calling the exclusive ordering apis: ```rust // Old (0.8) app.add_system(some_system.exclusive_system().at_end()); // New (0.9) app.add_system(some_system.at_end()); ``` Query state in exclusive systems can now be cached via ExclusiveSystemParams, which should be preferred for clarity and performance reasons: ```rust // Old (0.8) fn some_system(world: &mut World) { let mut transforms = world.query::<&Transform>(); for transform in transforms.iter(world) { } } // New (0.9) fn some_system(world: &mut World, transforms: &mut QueryState<&Transform>) { for transform in transforms.iter(world) { } } ```
…arams (bevyengine#6083) # Objective The [Stageless RFC](bevyengine/rfcs#45) involves allowing exclusive systems to be referenced and ordered relative to parallel systems. We've agreed that unifying systems under `System` is the right move. This is an alternative to bevyengine#4166 (see rationale in the comments I left there). Note that this builds on the learnings established there (and borrows some patterns). ## Solution This unifies parallel and exclusive systems under the shared `System` trait, removing the old `ExclusiveSystem` trait / impls. This is accomplished by adding a new `ExclusiveFunctionSystem` impl similar to `FunctionSystem`. It is backed by `ExclusiveSystemParam`, which is similar to `SystemParam`. There is a new flattened out SystemContainer api (which cuts out a lot of trait and type complexity). This means you can remove all cases of `exclusive_system()`: ```rust // before commands.add_system(some_system.exclusive_system()); // after commands.add_system(some_system); ``` I've also implemented `ExclusiveSystemParam` for `&mut QueryState` and `&mut SystemState`, which makes this possible in exclusive systems: ```rust fn some_exclusive_system( world: &mut World, transforms: &mut QueryState<&Transform>, state: &mut SystemState<(Res<Time>, Query<&Player>)>, ) { for transform in transforms.iter(world) { println!("{transform:?}"); } let (time, players) = state.get(world); for player in players.iter() { println!("{player:?}"); } } ``` Note that "exclusive function systems" assume `&mut World` is present (and the first param). I think this is a fair assumption, given that the presence of `&mut World` is what defines the need for an exclusive system. I added some targeted SystemParam `static` constraints, which removed the need for this: ``` rust fn some_exclusive_system(state: &mut SystemState<(Res<'static, Time>, Query<&'static Player>)>) {} ``` ## Related - bevyengine#2923 - bevyengine#3001 - bevyengine#3946 ## Changelog - `ExclusiveSystem` trait (and implementations) has been removed in favor of sharing the `System` trait. - `ExclusiveFunctionSystem` and `ExclusiveSystemParam` were added, enabling flexible exclusive function systems - `&mut SystemState` and `&mut QueryState` now implement `ExclusiveSystemParam` - Exclusive and parallel System configuration is now done via a unified `SystemDescriptor`, `IntoSystemDescriptor`, and `SystemContainer` api. ## Migration Guide Calling `.exclusive_system()` is no longer required (or supported) for converting exclusive system functions to exclusive systems: ```rust // Old (0.8) app.add_system(some_exclusive_system.exclusive_system()); // New (0.9) app.add_system(some_exclusive_system); ``` Converting "normal" parallel systems to exclusive systems is done by calling the exclusive ordering apis: ```rust // Old (0.8) app.add_system(some_system.exclusive_system().at_end()); // New (0.9) app.add_system(some_system.at_end()); ``` Query state in exclusive systems can now be cached via ExclusiveSystemParams, which should be preferred for clarity and performance reasons: ```rust // Old (0.8) fn some_system(world: &mut World) { let mut transforms = world.query::<&Transform>(); for transform in transforms.iter(world) { } } // New (0.9) fn some_system(world: &mut World, transforms: &mut QueryState<&Transform>) { for transform in transforms.iter(world) { } } ```
…arams (bevyengine#6083) # Objective The [Stageless RFC](bevyengine/rfcs#45) involves allowing exclusive systems to be referenced and ordered relative to parallel systems. We've agreed that unifying systems under `System` is the right move. This is an alternative to bevyengine#4166 (see rationale in the comments I left there). Note that this builds on the learnings established there (and borrows some patterns). ## Solution This unifies parallel and exclusive systems under the shared `System` trait, removing the old `ExclusiveSystem` trait / impls. This is accomplished by adding a new `ExclusiveFunctionSystem` impl similar to `FunctionSystem`. It is backed by `ExclusiveSystemParam`, which is similar to `SystemParam`. There is a new flattened out SystemContainer api (which cuts out a lot of trait and type complexity). This means you can remove all cases of `exclusive_system()`: ```rust // before commands.add_system(some_system.exclusive_system()); // after commands.add_system(some_system); ``` I've also implemented `ExclusiveSystemParam` for `&mut QueryState` and `&mut SystemState`, which makes this possible in exclusive systems: ```rust fn some_exclusive_system( world: &mut World, transforms: &mut QueryState<&Transform>, state: &mut SystemState<(Res<Time>, Query<&Player>)>, ) { for transform in transforms.iter(world) { println!("{transform:?}"); } let (time, players) = state.get(world); for player in players.iter() { println!("{player:?}"); } } ``` Note that "exclusive function systems" assume `&mut World` is present (and the first param). I think this is a fair assumption, given that the presence of `&mut World` is what defines the need for an exclusive system. I added some targeted SystemParam `static` constraints, which removed the need for this: ``` rust fn some_exclusive_system(state: &mut SystemState<(Res<'static, Time>, Query<&'static Player>)>) {} ``` ## Related - bevyengine#2923 - bevyengine#3001 - bevyengine#3946 ## Changelog - `ExclusiveSystem` trait (and implementations) has been removed in favor of sharing the `System` trait. - `ExclusiveFunctionSystem` and `ExclusiveSystemParam` were added, enabling flexible exclusive function systems - `&mut SystemState` and `&mut QueryState` now implement `ExclusiveSystemParam` - Exclusive and parallel System configuration is now done via a unified `SystemDescriptor`, `IntoSystemDescriptor`, and `SystemContainer` api. ## Migration Guide Calling `.exclusive_system()` is no longer required (or supported) for converting exclusive system functions to exclusive systems: ```rust // Old (0.8) app.add_system(some_exclusive_system.exclusive_system()); // New (0.9) app.add_system(some_exclusive_system); ``` Converting "normal" parallel systems to exclusive systems is done by calling the exclusive ordering apis: ```rust // Old (0.8) app.add_system(some_system.exclusive_system().at_end()); // New (0.9) app.add_system(some_system.at_end()); ``` Query state in exclusive systems can now be cached via ExclusiveSystemParams, which should be preferred for clarity and performance reasons: ```rust // Old (0.8) fn some_system(world: &mut World) { let mut transforms = world.query::<&Transform>(); for transform in transforms.iter(world) { } } // New (0.9) fn some_system(world: &mut World, transforms: &mut QueryState<&Transform>) { for transform in transforms.iter(world) { } } ```
Objective
.system
#2398.exclusive_system()
optional (FunctionSystem
handles all systems now)Solution
SemiSafeCell<'w, World>
MaybeUnsafeCell<'w, World>
and have it replace&'w World
in internal access methodsSystemParamState
so we can (mostly) statically ensure soundness and detect exclusive systems&mut World
as a system paramExclusiveSystem*
types and traitsmark(we might need it later to coerce systems into being exclusive).exclusive_system()
method as deprecatedRelated