use anyhow::anyhow;
use bevy::{
ecs::{
event::ManualEventReader,
query::WorldQuery,
system::{SystemParam, SystemState},
},
prelude::*,
utils::HashSet,
};
use wasmer::{imports, Imports, Instance, Module};
use crate::{world_pointer::WorldPointer, WasmScript, WasmerStore};
pub trait WasmScriptComponent: Component {
type ImportQueriedComponents: WorldQuery;
type ImportResources: SystemParam;
fn get_imports_from_world(_wasmer_store: &mut WasmerStore, _world: &WorldPointer) -> Imports {
imports! {}
}
fn get_wasm_script_handle(&self) -> &Handle<WasmScript>;
fn instantiate(
world_pointer: &WorldPointer,
wasmer_store: &mut WasmerStore,
module: &Module,
) -> Result<Instance, anyhow::Error> {
let imports = Self::get_imports_from_world(wasmer_store, world_pointer);
let instance = Instance::new(&mut wasmer_store.0, module, &imports)?;
Ok(instance)
}
}
fn get_modified_script_assets<S: WasmScriptComponent>(
world: &mut World,
) -> Vec<Handle<WasmScript>> {
let mut result = Vec::new();
{
let ev_asset_loaded = world
.get_resource_mut::<Events<AssetEvent<WasmScript>>>()
.unwrap();
let mut event_reader = ManualEventReader::<AssetEvent<WasmScript>>::default();
for asset in event_reader.iter(&ev_asset_loaded) {
if let AssetEvent::Modified { handle } = asset {
result.push(handle.clone());
}
}
}
if result.len() > 0 {
let mut scripts_on_entities: QueryState<&S> = world.query();
let mut s_handles = HashSet::new();
{
for component in scripts_on_entities.iter(&world) {
s_handles.insert(component.get_wasm_script_handle());
}
}
result.retain(|updated_script_handle| s_handles.contains(updated_script_handle));
}
result
}
fn get_added_script_assets<S: WasmScriptComponent>(world: &mut World) -> Vec<Handle<WasmScript>> {
let mut s_handles = HashSet::new();
{
for changed in world
.query_filtered::<&S, Or<(Changed<S>, Added<S>)>>()
.iter(world)
{
s_handles.insert(changed.get_wasm_script_handle().clone());
}
}
s_handles.drain().collect::<Vec<Handle<WasmScript>>>()
}
fn instantiate_if_compiled<S: WasmScriptComponent>(
world: &mut World,
wasm_script_handle: Handle<WasmScript>,
) -> Option<(String, Instance)> {
unsafe {
let world_pointer = WorldPointer::new(world).clone();
let mut state =
SystemState::<(ResMut<Assets<WasmScript>>, ResMut<WasmerStore>)>::new(world);
let (mut wasm_assets, mut wasmer_store) = state.get_mut(world);
let wasm_script = wasm_assets.get_mut(&wasm_script_handle)?;
let name = wasm_script.name();
if let WasmScript::Compiled(module) = wasm_script {
bevy::log::warn!("Received compiled module {}...", name);
match S::instantiate(&world_pointer, &mut wasmer_store, module) {
Ok(instance) => {
bevy::log::warn!("Instantiated module {}...", name);
Some((module.name().unwrap_or("").to_string(), instance))
}
Err(err) => {
bevy::log::error!("Could not instantiate {}: {}", name, err);
None
}
}
} else {
None
}
}
}
pub fn instantiate_wasm_component_scripts<S: WasmScriptComponent>(world: &mut World) {
for script_asset in get_modified_script_assets::<S>(world) {
if let Some((name, instance)) = instantiate_if_compiled::<S>(world, script_asset.clone()) {
world
.get_resource_mut::<Assets<WasmScript>>()
.unwrap()
.set_untracked(script_asset, WasmScript::Instantiated(name, instance));
}
}
for script_asset in get_added_script_assets::<S>(world) {
if let Some((name, instance)) = instantiate_if_compiled::<S>(world, script_asset.clone()) {
world
.get_resource_mut::<Assets<WasmScript>>()
.unwrap()
.set_untracked(script_asset, WasmScript::Instantiated(name, instance));
}
}
}