Repository metrics
- Stars
- (126 stars)
- PR merge metrics
- (PR metrics pending)
Description
Currently all system params are declared in the setup. For example, once we implement https://github.com/wasvy-org/wasvy/issues/57 and https://github.com/wasvy-org/wasvy/issues/54, a user might write the following rust mod:
fn setup(app: App) {
let system = System::new("system");
system.add_commands();
system.add_single("MyResource");
app.add_systems(&Schedule::ModStartup, vec![system]);
}
fn system(commands: Commands, component: Single) {
let my_resource = MyResource::new(component); // Generated by wasvy authoring
...
}
This has several problems.
- It's prone to error as pointed out by @shishanyue here: https://github.com/wasvy-org/wasvy/issues/52#issuecomment-4186110398
- It's redundant. Wasmtime generates type information for exported methods that we can read back to determine params
- It's not what a user coming from bevy would expect
It should be possible to write:
fn setup(app: App) {
let system = System::new("system");
app.add_systems(&Schedule::ModStartup, vec![system]);
}
// `Commands` resource is a know system param, so wasvy can resolve it to `Param::Commands`
// `MyResource` resource is a known component reflected by authoring, so wasvy knows its type path and can resolve it to `Param::Single(TypePath)`
fn system(commands: Commands, my_resource: MyResource) {
...
}
The modder would still be expected to provide param information for situations where params cannot be resolved.
fn setup(app: App) {
let system = System::new("system");
// TBD, api could look something like:
system.param(0, Param::Query(vec![QueryFor::Mut("Transform"), QueryFor::With("Camera")]);
system.param(2, Param::Single("MyResource"));
app.add_systems(&Schedule::ModStartup, vec![system]);
}
fn system(camera_transform: Query, commands: Commands, component: Single) {
...
}
To make this work, after a mod constructs a new system resource (System::new("system")) wasvy must introspect the system export of the wasm binary before scheduling the system. This can be done with some code like like this:
let instance = todo!("Get the wasm instance somehow");
let system_name = system.name; // ie: "system" in our examples above
let system = instance
.get_func(&mut store, system_name)
.ok_or_else(|| anyhow!("export {system_name} not found"))?;
let user_params: &Vec<Option<Param>> = system.params; // params provided by the user in setup, some might be missing
let mut params: Vec<Param> = Vec::new();
let mut errors: Vec<Anyhow> = Vec::new();
for (index, (name, ty)) in system.ty(&store).params().enumerate() {
let user_param = user_params.get(index).clone();
let detected_param = match ty {
Type::Own(ty) | Type::Borrow(ty) => {
if is_commands(ty) {
Some(Param::Commands)
} else if is_query(ty) {
// Unknown QueryFor
None
} else if is_any_component(ty) {
// A Component could have any type path, so we can't resolver it automatically
None
} else if let Some(type_name) = reflected_component(ty) {
Some(Param::ReflectedSingle(type_name)) // Produces the correct resource, not a generic `component`
} else {
errors.push(anyhow!("System {system_name} param {index} \"{name}\" is unknown resource type {ty:?}"));
continue;
}
}
_ => {
errors.push(anyhow!("System {system_name} param {index} \"{name}\" is unexpected type {ty:?}"));
continue;
}
};
let param = user_param.or(detected_param);
if let Some(param) = user_param.or(detected_param) {
params.push(param);
} else {
errors.push(anyhow!("System {system_name} could not resolve param {index}. Please set one via System::param."));
}
}
// Instead of exiting early, we collect all errors and log these to the user
if !errors.is_empty() {
bail!(SystemErrors(errors));
}
The code above is just to get started. Ideally we'd return all errors from all systems instead of bailing at the first one.
Relevant place in the codebase: