Terrafier/crates/wasm-host/src/plugin.rs

187 lines
5.7 KiB
Rust

use std::path::Path;
use terrafier_core::model::tile::Tile;
use terrafier_core::plugins::TileSourcePlugin;
use wasmtime::*;
use crate::context::WorldContext;
use crate::error::WasmHostError;
use crate::host::register_host_functions;
pub trait WasmPlugin {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn plugin_type(&self) -> u32;
}
pub struct WasmTileSourcePlugin {
engine: Engine,
module: Module,
name: String,
version: String,
}
impl WasmTileSourcePlugin {
pub fn from_file(path: &Path) -> Result<Self, WasmHostError> {
let engine = Engine::default();
let wasm_bytes = std::fs::read(path).map_err(|e| {
WasmHostError::LoadError(anyhow::anyhow!("Failed to read .wasm file: {}", e))
})?;
let module = Module::from_binary(&engine, &wasm_bytes)
.map_err(|e| WasmHostError::LoadError(anyhow::anyhow!("Invalid WASM module: {}", e)))?;
let mut linker = Linker::new(&engine);
register_host_functions(&mut linker).map_err(|e| {
WasmHostError::LoadError(anyhow::anyhow!("Failed to register host functions: {}", e))
})?;
let dummy_ctx = WorldContext {
tile: Tile::new(0, 0, -64, 320),
seed: 0,
tile_x: 0,
tile_z: 0,
hm_offset: 0,
ter_offset: 0,
wat_offset: 0,
error: None,
};
let mut store = Store::new(&engine, dummy_ctx);
let instance = linker.instantiate(&mut store, &module).map_err(|e| {
WasmHostError::LoadError(anyhow::anyhow!("Failed to instantiate module: {}", e))
})?;
let memory = instance
.get_memory(&mut store, "memory")
.ok_or_else(|| WasmHostError::MissingExport("memory".into()))?;
let name = Self::read_string(&mut store, &instance, &memory, "name_ptr", "name_len")?;
let version =
Self::read_string(&mut store, &instance, &memory, "version_ptr", "version_len")?;
Ok(Self {
engine,
module,
name,
version,
})
}
fn read_string(
store: &mut Store<WorldContext>,
instance: &Instance,
memory: &Memory,
ptr_func: &str,
len_func: &str,
) -> Result<String, WasmHostError> {
let ptr = instance
.get_typed_func::<(), i32>(&mut *store, ptr_func)
.map_err(|_| WasmHostError::MissingExport(ptr_func.into()))?
.call(&mut *store, ())
.map_err(|e| WasmHostError::PluginError(e.to_string()))?;
let len = instance
.get_typed_func::<(), i32>(&mut *store, len_func)
.map_err(|_| WasmHostError::MissingExport(len_func.into()))?
.call(&mut *store, ())
.map_err(|e| WasmHostError::PluginError(e.to_string()))?;
let mut buf = vec![0u8; len as usize];
memory
.read(&*store, ptr as usize, &mut buf)
.map_err(|e| WasmHostError::MemoryError(e.to_string()))?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
fn make_instance(
&self,
tile: &Tile,
seed: u64,
tile_x: i32,
tile_z: i32,
) -> Result<(Store<WorldContext>, Instance), WasmHostError> {
let mut linker = Linker::new(&self.engine);
register_host_functions(&mut linker).map_err(WasmHostError::LoadError)?;
let ctx = WorldContext {
tile: tile.clone(),
seed,
tile_x,
tile_z,
hm_offset: 0,
ter_offset: 0,
wat_offset: 0,
error: None,
};
let mut store = Store::new(&self.engine, ctx);
let instance = linker
.instantiate(&mut store, &self.module)
.map_err(|e| WasmHostError::LoadError(e.into()))?;
Ok((store, instance))
}
}
impl WasmPlugin for WasmTileSourcePlugin {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
&self.version
}
fn plugin_type(&self) -> u32 {
3
}
}
impl TileSourcePlugin for WasmTileSourcePlugin {
fn name(&self) -> &'static str {
Box::leak(self.name.clone().into_boxed_str())
}
fn version(&self) -> &'static str {
Box::leak(self.version.clone().into_boxed_str())
}
fn generate_tile(&self, tile: &mut Tile, seed: u64) {
let tx = tile.x;
let tz = tile.z;
let tile_clone = tile.clone();
let (mut store, instance) = match self.make_instance(&tile_clone, seed, tx, tz) {
Ok(v) => v,
Err(e) => {
log::error!(
"Failed to create WASM instance for plugin '{}': {}",
self.name,
e
);
return;
}
};
let Ok(gen_func) =
instance.get_typed_func::<(i32, i32, i32, i32), i32>(&mut store, "generate_tile")
else {
log::error!(
"WASM plugin '{}' does not export 'generate_tile'",
self.name
);
return;
};
let seed_lo = (seed & 0xFFFF_FFFF) as i32;
let seed_hi = ((seed >> 32) & 0xFFFF_FFFF) as i32;
match gen_func.call(&mut store, (tx, tz, seed_lo, seed_hi)) {
Ok(0) => {
*tile = store.data().tile.clone();
}
Ok(code) => {
log::error!(
"WASM plugin '{}' generate_tile returned error code: {}",
self.name,
code
);
}
Err(e) => {
log::error!("WASM plugin '{}' generate_tile trap: {}", self.name, e);
}
}
}
}