use std::fmt;
use anyhow::{Context, Result, anyhow};
use bevy_asset::{Asset, AssetId, AssetLoader, Assets, LoadContext, io::Reader};
use bevy_ecs::{change_detection::Tick, prelude::*};
use bevy_reflect::TypePath;
use wasmtime::component::{Component, InstancePre, Val};
use crate::{
access::ModAccess,
cleanup::DespawnModEntities,
engine::{Engine, Linker},
host::{WasmApp, WasmHost},
mods::ModDespawnBehaviour,
runner::{Config, ConfigRunSystem, ConfigSetup, Runner},
system::AddSystems,
};
#[derive(Asset, TypePath)]
pub struct ModAsset {
version: Option<Tick>,
instance_pre: InstancePre<WasmHost>,
}
const SETUP: &str = "setup";
impl ModAsset {
pub(crate) async fn new(loader: &ModAssetLoader, reader: &mut dyn Reader) -> Result<Self> {
let mut bytes = vec![];
reader.read_to_end(&mut bytes).await?;
let component = Component::from_binary(loader.linker.engine(), &bytes)?;
let instance_pre = loader.linker.instantiate_pre(&component)?;
Ok(Self {
version: None,
instance_pre,
})
}
pub(crate) fn version(&self) -> Option<Tick> {
self.version
}
pub(crate) fn initiate(
world: &mut World,
asset_id: &AssetId<ModAsset>,
mod_id: Entity,
mod_name: &str,
accesses: &[ModAccess],
) -> Result<()> {
let change_tick = world.change_tick();
let mut assets = world
.get_resource_mut::<Assets<Self>>()
.expect("ModAssets be registered");
let asset = assets.get_mut(*asset_id).ok_or(AssetNotFound)?;
let asset_version = match asset.version {
Some(version) => version,
None => {
asset.version = Some(change_tick);
change_tick
}
};
let instance_pre = asset.instance_pre.clone();
if ModDespawnBehaviour::should_despawn_entities(world) {
let (entities, mut commands) = world.entities_and_commands();
let despawn = entities
.get(mod_id)
.expect("Mod entity exists")
.get::<DespawnModEntities>()
.expect(
"DespawnModEntities should have been registered as a required componet for Mod",
);
for source_entity in despawn.iter() {
commands.entity(source_entity).try_despawn();
}
}
let engine = world
.get_resource::<Engine>()
.expect("Engine should never be removed from world");
let mut runner = Runner::new(engine);
let mut systems = AddSystems::default();
let config = Config::Setup(ConfigSetup {
world,
add_systems: &mut systems,
});
let app = runner.new_resource(WasmApp).expect("Table has space left");
call(
&mut runner,
&instance_pre,
config,
SETUP,
&[Val::Resource(app)],
&mut [],
)?;
systems.add_systems(
world,
accesses,
runner.table(),
mod_id,
mod_name,
asset_id,
&asset_version,
)?;
Ok(())
}
pub(crate) fn run_system<'a, 'b, 'c, 'd, 'e, 'f, 'g>(
&self,
runner: &mut Runner,
name: &str,
config: ConfigRunSystem<'a, 'b, 'c, 'd, 'e, 'f, 'g>,
params: &[Val],
) -> Result<()> {
let config = Config::RunSystem(config);
call(runner, &self.instance_pre, config, name, params, &mut [])
}
}
#[derive(Debug)]
pub(crate) struct AssetNotFound;
impl std::error::Error for AssetNotFound {}
impl fmt::Display for AssetNotFound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Asset not found. Maybe it hasn't loaded yet.")
}
}
fn call(
runner: &mut Runner,
instance_pre: &InstancePre<WasmHost>,
config: Config,
name: &str,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
runner.use_store(config, move |mut store| {
let instance = instance_pre
.instantiate(&mut store)
.context("Failed to instantiate component")?;
let func = instance
.get_func(&mut store, name)
.ok_or(anyhow!("Missing {name} function"))?;
func.call(&mut store, params, results)
.context("Failed to run the desired wasm function")?;
Ok(())
})
}
#[derive(TypePath)]
pub struct ModAssetLoader {
pub(crate) linker: Linker,
}
impl AssetLoader for ModAssetLoader {
type Asset = ModAsset;
type Settings = ();
type Error = anyhow::Error;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset> {
let asset = ModAsset::new(self, reader).await?;
Ok(asset)
}
fn extensions(&self) -> &[&str] {
&["wasm"]
}
}