use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use wasmtime::component::Component;
use super::engine::WasmEngine;
use super::sync_bindings::PluginPre;
use super::sync_controller::WasmController;
use super::sync_host_state::HostState;
use crate::plugin::error::PluginError;
#[cfg(feature = "plugin-wasm-async")]
use super::async_controller::{AsyncPluginPreBuilt, AsyncWasmController};
#[cfg(feature = "plugin-wasm-async")]
use super::async_runtime::{AsyncMode, AsyncRuntime};
pub struct WasmPluginCache {
sync_engine: Arc<WasmEngine>,
sync_plugins: HashMap<PathBuf, CachedSyncPlugin>,
#[cfg(feature = "plugin-wasm-async")]
async_mode: AsyncMode,
#[cfg(feature = "plugin-wasm-async")]
async_state: Option<AsyncCacheState>,
}
struct CachedSyncPlugin {
#[allow(dead_code)]
component: Component,
pre: PluginPre<HostState>,
}
#[cfg(feature = "plugin-wasm-async")]
struct AsyncCacheState {
engine: Arc<WasmEngine>,
runtime: Arc<AsyncRuntime>,
plugins: HashMap<PathBuf, AsyncPluginPreBuilt>,
}
impl WasmPluginCache {
pub fn new() -> Result<Self, PluginError> {
let sync_engine = Arc::new(WasmEngine::new_sync()?);
Ok(Self {
sync_engine,
sync_plugins: HashMap::new(),
#[cfg(feature = "plugin-wasm-async")]
async_mode: AsyncMode::Deterministic,
#[cfg(feature = "plugin-wasm-async")]
async_state: None,
})
}
#[cfg(feature = "plugin-wasm-async")]
pub fn new_with_async_mode(async_mode: AsyncMode) -> Result<Self, PluginError> {
let sync_engine = Arc::new(WasmEngine::new_sync()?);
Ok(Self {
sync_engine,
sync_plugins: HashMap::new(),
async_mode,
async_state: None,
})
}
pub fn sync_engine(&self) -> &Arc<WasmEngine> {
&self.sync_engine
}
pub fn build_sync_controller(
&mut self,
path: &Path,
label: &str,
config: &str,
) -> Result<WasmController, PluginError> {
let pre = self.get_or_load_sync(path)?;
WasmController::new(pre, label, config)
}
pub fn build_controller(
&mut self,
path: &Path,
label: &str,
config: &str,
) -> Result<WasmController, PluginError> {
self.build_sync_controller(path, label, config)
}
fn get_or_load_sync(&mut self, path: &Path) -> Result<&PluginPre<HostState>, PluginError> {
if !self.sync_plugins.contains_key(path) {
let bytes = std::fs::read(path).map_err(|e| {
PluginError::Init(format!("cannot read WASM at '{}': {e}", path.display()))
})?;
let component = Component::new(self.sync_engine.inner(), &bytes).map_err(|e| {
PluginError::Init(format!("WASM compile failed for '{}': {e}", path.display()))
})?;
let pre = WasmController::prepare(&self.sync_engine, &component)?;
self.sync_plugins
.insert(path.to_path_buf(), CachedSyncPlugin { component, pre });
}
Ok(&self
.sync_plugins
.get(path)
.expect("just inserted if missing")
.pre)
}
}
#[cfg(feature = "plugin-wasm-async")]
impl WasmPluginCache {
pub fn build_async_controller(
&mut self,
path: &Path,
label: &str,
config: &str,
) -> Result<AsyncWasmController, PluginError> {
let built = self.get_or_load_async(path)?;
AsyncWasmController::new(built, label, config)
}
pub fn async_engine(&mut self) -> Result<&Arc<WasmEngine>, PluginError> {
self.ensure_async_state()?;
Ok(&self.async_state.as_ref().unwrap().engine)
}
pub fn async_runtime(&mut self) -> Result<&Arc<AsyncRuntime>, PluginError> {
self.ensure_async_state()?;
Ok(&self.async_state.as_ref().unwrap().runtime)
}
fn ensure_async_state(&mut self) -> Result<(), PluginError> {
if self.async_state.is_none() {
let engine = Arc::new(WasmEngine::new_async()?);
let runtime = Arc::new(AsyncRuntime::new(self.async_mode)?);
self.async_state = Some(AsyncCacheState {
engine,
runtime,
plugins: HashMap::new(),
});
}
Ok(())
}
fn get_or_load_async(&mut self, path: &Path) -> Result<&AsyncPluginPreBuilt, PluginError> {
self.ensure_async_state()?;
let state = self.async_state.as_mut().unwrap();
if !state.plugins.contains_key(path) {
let bytes = std::fs::read(path).map_err(|e| {
PluginError::Init(format!("cannot read WASM at '{}': {e}", path.display()))
})?;
let component = Component::new(state.engine.inner(), &bytes).map_err(|e| {
PluginError::Init(format!(
"async WASM compile failed for '{}': {e}",
path.display()
))
})?;
let built = AsyncPluginPreBuilt::new(&state.engine, &state.runtime, &component)?;
state.plugins.insert(path.to_path_buf(), built);
}
Ok(state.plugins.get(path).expect("just inserted if missing"))
}
}