use anyhow::Result;
use bevy_asset::{AssetId, Assets};
use bevy_ecs::{
change_detection::Tick,
error::Result as BevyResult,
prelude::*,
resource::Resource as BevyResource,
schedule::{ScheduleConfigs, ScheduleLabel},
system::{BoxedSystem, Commands, LocalBuilder, ParamBuilder, ParamSetBuilder, Query},
world::FilteredEntityMut,
};
use bevy_log::prelude::*;
use wasmtime::component::{Resource, Val};
use wasmtime_wasi::ResourceTable;
use crate::{
access::ModAccess,
asset::ModAsset,
bindings::wasvy::ecs::app::{QueryFor, Schedule},
cleanup::InsertDespawnComponent,
component::WasmComponentRegistry,
engine::Engine,
host::{WasmCommands, WasmQuery, WasmSystem},
methods::FunctionIndex,
mods::ModSystemSet,
query::{QueryId, QueryIdGenerator, QueryResolver, create_query_builder},
runner::{ConfigRunSystem, Runner},
serialize::CodecResource,
};
#[derive(Default)]
pub(crate) struct AddSystems(Vec<(Schedule, Vec<Resource<WasmSystem>>)>);
impl AddSystems {
pub(crate) fn push(&mut self, schedule: Schedule, systems: Vec<Resource<WasmSystem>>) {
self.0.push((schedule, systems));
}
pub(crate) fn add_systems(
self,
world: &mut World,
accesses: &[ModAccess],
table: &ResourceTable,
mod_id: Entity,
mod_name: &str,
asset_id: &AssetId<ModAsset>,
asset_version: &Tick,
) -> Result<()> {
for access in accesses {
let mod_schedules = access.schedules(world);
for (schedule, systems) in self.0.iter() {
let Some(schedule) = mod_schedules
.evaluate(schedule)
.map(|schedule| schedule.schedule_label())
else {
warn!(
"Mod tried adding systems to schedule {schedule:?}, but that schedule is not enabled. See ModSchedules docs."
);
continue;
};
for system in systems
.iter()
.map(|system| table.get(system).expect("Resource not be dropped"))
{
Self::add_system(
schedule,
system,
world,
mod_id,
mod_name,
asset_id,
asset_version,
access,
)?;
}
}
}
Ok(())
}
fn add_system(
schedule: impl ScheduleLabel,
system: &WasmSystem,
world: &mut World,
mod_id: Entity,
mod_name: &str,
asset_id: &AssetId<ModAsset>,
asset_version: &Tick,
access: &ModAccess,
) -> Result<()> {
let schedule_config = Self::schedule(
system,
world,
mod_id,
mod_name,
asset_id,
asset_version,
access,
)?
.in_set(ModSystemSet::All)
.in_set(ModSystemSet::Mod(mod_id))
.in_set(ModSystemSet::Access(*access));
world
.get_resource_mut::<Schedules>()
.expect("running in an App")
.add_systems(schedule, schedule_config);
Ok(())
}
pub(crate) fn schedule(
sys: &WasmSystem,
world: &mut World,
mod_id: Entity,
mod_name: &str,
asset_id: &AssetId<ModAsset>,
asset_version: &Tick,
access: &ModAccess,
) -> Result<ScheduleConfigs<BoxedSystem>> {
let built_params = BuiltParam::new_vec(&sys.params);
let query_resolver = QueryResolver::new(&sys.params, world)?;
let insert_despawn_component = InsertDespawnComponent::new(mod_id, world);
let input = Input {
mod_name: mod_name.to_string(),
system_name: sys.name.clone(),
asset_id: *asset_id,
asset_version: *asset_version,
built_params,
query_resolver,
access: *access,
insert_despawn_component,
};
let filtered_access = access.filtered_access(world);
let mut queries = Vec::with_capacity(sys.params.len());
for items in sys.params.iter().filter_map(Param::filter_query) {
queries.push(create_query_builder(items, world, filtered_access.clone())?);
}
let system = (
LocalBuilder(input),
LocalBuilder(Vec::with_capacity(queries.len())),
ParamBuilder,
ParamBuilder,
ParamBuilder,
ParamBuilder,
ParamBuilder,
ParamBuilder,
ParamBuilder,
ParamSetBuilder(queries),
)
.build_state(world)
.build_system(dynamic_system)
.with_name(format!("wasvy[{mod_name}]::{}", sys.name));
let boxed_system = Box::new(IntoSystem::into_system(system));
let mut schedule_config = boxed_system
.in_set(sys.id);
for after in sys.after.iter() {
schedule_config = schedule_config.after(*after);
}
Ok(schedule_config)
}
}
struct Input {
mod_name: String,
system_name: String,
asset_id: AssetId<ModAsset>,
asset_version: Tick,
built_params: Vec<BuiltParam>,
query_resolver: QueryResolver,
access: ModAccess,
insert_despawn_component: InsertDespawnComponent,
}
impl FromWorld for Input {
fn from_world(_: &mut World) -> Self {
unreachable!("Input is created with LocalBuilder")
}
}
fn dynamic_system(
input: Local<Input>,
mut params: Local<Vec<Val>>,
assets: Res<Assets<ModAsset>>,
engine: Res<Engine>,
type_registry: Res<AppTypeRegistry>,
codec: Res<CodecResource>,
wasm_registry: Res<WasmComponentRegistry>,
function_index: Res<FunctionIndex>,
mut commands: Commands,
mut queries: ParamSet<Vec<Query<FilteredEntityMut>>>,
) -> BevyResult {
let Some(asset) = assets.get(input.asset_id) else {
return Ok(());
};
if asset.version() != Some(input.asset_version) {
return Ok(());
}
let mut runner = Runner::new(&engine);
initialize_params(&mut params, &input.built_params, &mut runner)?;
trace!(
"Running system \"{}\" from \"{}\"",
input.system_name, input.mod_name
);
asset.run_system(
&mut runner,
&input.system_name,
ConfigRunSystem {
commands: &mut commands,
type_registry: &type_registry,
codec: &codec,
wasm_registry: &wasm_registry,
function_index: &function_index,
queries: &mut queries,
query_resolver: &input.query_resolver,
access: input.access,
insert_despawn_component: input.insert_despawn_component,
},
¶ms[..],
)?;
Ok(())
}
pub(crate) enum Param {
Commands,
Query(Vec<QueryFor>),
}
impl Param {
pub(crate) fn filter_query(&self) -> Option<&Vec<QueryFor>> {
match self {
Param::Query(items) => Some(items),
_ => None,
}
}
}
enum BuiltParam {
Commands,
Query(QueryId),
}
impl BuiltParam {
fn new_vec(params: &[Param]) -> Vec<Self> {
let mut ids = QueryIdGenerator::default();
params
.iter()
.map(|param| match param {
Param::Commands => BuiltParam::Commands,
Param::Query(_) => BuiltParam::Query(ids.generate()),
})
.collect()
}
}
fn initialize_params(
params: &mut Vec<Val>,
source: &[BuiltParam],
runner: &mut Runner,
) -> Result<()> {
params.clear();
for param in source.iter() {
let resource = match param {
BuiltParam::Commands => runner.new_resource(WasmCommands),
BuiltParam::Query(id) => runner.new_resource(WasmQuery::new(*id)),
}?;
params.push(Val::Resource(resource));
}
Ok(())
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct DynamicSystemId(usize);
impl DynamicSystemId {
pub(crate) fn new(world: &mut World) -> Self {
world.init_resource::<DynamicSystemSetCount>();
let mut count = world
.get_resource_mut::<DynamicSystemSetCount>()
.expect("SystemIdentifierCount to be initialized");
let identifier = DynamicSystemId(count.0);
count.0 += 1;
identifier
}
}
impl SystemSet for DynamicSystemId {
fn is_anonymous(&self) -> bool {
true
}
fn dyn_clone(&self) -> Box<dyn SystemSet> {
Box::new(*self)
}
}
#[derive(Default, BevyResource)]
struct DynamicSystemSetCount(usize);