1use std::sync::{Arc, Mutex};
2
3use super::{
4 CommandResults,
5 errors::PluginModuleError,
6 memory_manager::{EcsAccessManager, EcsWorld},
7};
8use hashbrown::{HashMap, HashSet};
9use wasmtime::{
10 Config, Engine, Store,
11 component::{Component, Linker},
12};
13use wasmtime_wasi::WasiView;
14
15pub(crate) mod types_mod {
16 wasmtime::component::bindgen!({
17 path: "../../plugin/wit/veloren.wit",
18 world: "common-types",
19 });
20}
21
22wasmtime::component::bindgen!({
23 path: "../../plugin/wit/veloren.wit",
24 world: "plugin",
25 with: {
26 "veloren:plugin/types@0.0.1": types_mod::veloren::plugin::types,
27 "veloren:plugin/information@0.0.1/entity": Entity,
28 },
29});
30
31mod animation_plugin {
32 wasmtime::component::bindgen!({
33 path: "../../plugin/wit/veloren.wit",
34 world: "animation-plugin",
35 with: {
36 "veloren:plugin/types@0.0.1": super::types_mod::veloren::plugin::types,
37 },
38 });
39}
40
41mod server_plugin {
42 wasmtime::component::bindgen!({
43 path: "../../plugin/wit/veloren.wit",
44 world: "server-plugin",
45 with: {
46 "veloren:plugin/types@0.0.1": super::types_mod::veloren::plugin::types,
47 "veloren:plugin/information@0.0.1/entity": super::Entity,
48 },
49 });
50}
51
52pub struct Entity {
53 uid: common::uid::Uid,
54}
55
56pub use animation::Body;
57use exports::veloren::plugin::animation;
58pub use types_mod::veloren::plugin::types::{
59 self, CharacterState, Dependency, Skeleton, Transform,
60};
61use veloren::plugin::{actions, information};
62
63type StoreType = wasmtime::Store<WasiHostCtx>;
64
65enum PluginWrapper {
67 Full(Plugin),
68 Animation(animation_plugin::AnimationPlugin),
69 Server(server_plugin::ServerPlugin),
70}
71
72impl PluginWrapper {
73 fn load_event<S: wasmtime::AsContextMut>(
74 &self,
75 store: S,
76 mode: common::resources::GameMode,
77 ) -> wasmtime::Result<()>
78 where
79 <S as wasmtime::AsContext>::Data: std::marker::Send,
80 {
81 let mode = match mode {
82 common::resources::GameMode::Server => types::GameMode::Server,
83 common::resources::GameMode::Client => types::GameMode::Client,
84 common::resources::GameMode::Singleplayer => types::GameMode::SinglePlayer,
85 };
86 match self {
87 PluginWrapper::Full(pl) => pl.veloren_plugin_events().call_load(store, mode),
88 PluginWrapper::Animation(pl) => pl.veloren_plugin_events().call_load(store, mode),
89 PluginWrapper::Server(pl) => pl.veloren_plugin_events().call_load(store, mode),
90 }
91 }
92
93 fn command_event<S: wasmtime::AsContextMut>(
94 &self,
95 store: S,
96 name: &str,
97 args: &[String],
98 player: types::Uid,
99 ) -> wasmtime::Result<Result<Vec<String>, String>>
100 where
101 <S as wasmtime::AsContext>::Data: std::marker::Send,
102 {
103 match self {
104 PluginWrapper::Full(pl) => pl
105 .veloren_plugin_server_events()
106 .call_command(store, name, args, player),
107 PluginWrapper::Animation(_) => Ok(Err("not implemented".into())),
108 PluginWrapper::Server(pl) => pl
109 .veloren_plugin_server_events()
110 .call_command(store, name, args, player),
111 }
112 }
113
114 fn player_join_event(
115 &self,
116 store: &mut StoreType,
117 name: &str,
118 uuid: (types::Uid, types::Uid),
119 ) -> wasmtime::Result<types::JoinResult> {
120 match self {
121 PluginWrapper::Full(pl) => pl
122 .veloren_plugin_server_events()
123 .call_join(store, name, uuid),
124 PluginWrapper::Animation(_) => Ok(types::JoinResult::None),
125 PluginWrapper::Server(pl) => pl
126 .veloren_plugin_server_events()
127 .call_join(store, name, uuid),
128 }
129 }
130
131 fn create_body(&self, store: &mut StoreType, bodytype: i32) -> Option<animation::Body> {
132 match self {
133 PluginWrapper::Full(pl) => {
134 let body_iface = pl.veloren_plugin_animation().body();
135 body_iface.call_constructor(store, bodytype).ok()
136 },
137 PluginWrapper::Animation(pl) => {
138 let body_iface = pl.veloren_plugin_animation().body();
139 body_iface.call_constructor(store, bodytype).ok()
140 },
141 PluginWrapper::Server(_) => None,
142 }
143 }
144
145 fn update_skeleton(
146 &self,
147 store: &mut StoreType,
148 body: animation::Body,
149 dep: types::Dependency,
150 time: f32,
151 ) -> Option<types::Skeleton> {
152 match self {
153 PluginWrapper::Full(pl) => {
154 let body_iface = pl.veloren_plugin_animation().body();
155 body_iface.call_update_skeleton(store, body, dep, time).ok()
156 },
157 PluginWrapper::Animation(pl) => {
158 let body_iface = pl.veloren_plugin_animation().body();
159 body_iface.call_update_skeleton(store, body, dep, time).ok()
160 },
161 PluginWrapper::Server(_) => None,
162 }
163 }
164}
165
166pub struct PluginModule {
168 ecs: Arc<EcsAccessManager>,
169 plugin: PluginWrapper,
170 store: Mutex<wasmtime::Store<WasiHostCtx>>,
171 name: String,
172}
173
174struct WasiHostCtx {
175 preview2_ctx: wasmtime_wasi::WasiCtx,
176 preview2_table: wasmtime::component::ResourceTable,
177 ecs: Arc<EcsAccessManager>,
178 registered_commands: HashSet<String>,
179 registered_bodies: HashMap<String, types::BodyIndex>,
180}
181
182impl wasmtime_wasi::WasiView for WasiHostCtx {
183 fn table(&mut self) -> &mut wasmtime::component::ResourceTable { &mut self.preview2_table }
184
185 fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx { &mut self.preview2_ctx }
186}
187
188impl information::Host for WasiHostCtx {}
189
190impl types::Host for WasiHostCtx {}
191
192impl actions::Host for WasiHostCtx {
193 fn register_command(&mut self, name: String) {
194 tracing::info!("Plugin registers /{name}");
195 self.registered_commands.insert(name);
196 }
197
198 fn player_send_message(&mut self, uid: actions::Uid, text: String) {
199 tracing::info!("Plugin sends message {text} to player {uid:?}");
200 }
201
202 fn register_animation(&mut self, name: String, id: types::BodyIndex) {
203 let _ = self.registered_bodies.insert(name, id);
204 }
205}
206
207impl information::HostEntity for WasiHostCtx {
208 fn find_entity(
209 &mut self,
210 uid: actions::Uid,
211 ) -> Result<wasmtime::component::Resource<information::Entity>, types::Error> {
212 self.table()
213 .push(Entity {
214 uid: common::uid::Uid(uid),
215 })
216 .map_err(|_err| types::Error::RuntimeError)
217 }
218
219 fn health(
220 &mut self,
221 self_: wasmtime::component::Resource<information::Entity>,
222 ) -> Result<information::Health, types::Error> {
223 let uid = self
224 .table()
225 .get(&self_)
226 .map_err(|_err| types::Error::RuntimeError)?
227 .uid;
228 self.ecs.with(|world| {
229 let world = world.ok_or(types::Error::EcsPointerNotAvailable)?;
230 let player = world
231 .id_maps
232 .uid_entity(uid)
233 .ok_or(types::Error::EcsEntityNotFound)?;
234 world
235 .health
236 .get(player)
237 .map(|health| information::Health {
238 current: health.current(),
239 base_max: health.base_max(),
240 maximum: health.maximum(),
241 })
242 .ok_or(types::Error::EcsComponentNotFound)
243 })
244 }
245
246 fn name(
247 &mut self,
248 self_: wasmtime::component::Resource<information::Entity>,
249 ) -> Result<String, types::Error> {
250 let uid = self
251 .table()
252 .get(&self_)
253 .map_err(|_err| types::Error::RuntimeError)?
254 .uid;
255 self.ecs.with(|world| {
256 let world = world.ok_or(types::Error::EcsPointerNotAvailable)?;
257 let player = world
258 .id_maps
259 .uid_entity(uid)
260 .ok_or(types::Error::EcsEntityNotFound)?;
261 Ok(world
262 .player
263 .get(player)
264 .ok_or(types::Error::EcsComponentNotFound)?
265 .alias
266 .to_owned())
267 })
268 }
269
270 fn drop(
271 &mut self,
272 rep: wasmtime::component::Resource<information::Entity>,
273 ) -> wasmtime::Result<()> {
274 Ok(self.table().delete(rep).map(|_entity| ())?)
275 }
276}
277
278struct InfoStream(String);
279
280impl wasmtime_wasi::HostOutputStream for InfoStream {
281 fn write(&mut self, bytes: bytes::Bytes) -> wasmtime_wasi::StreamResult<()> {
282 tracing::info!("{}: {}", self.0, String::from_utf8_lossy(bytes.as_ref()));
283 Ok(())
284 }
285
286 fn flush(&mut self) -> wasmtime_wasi::StreamResult<()> { Ok(()) }
287
288 fn check_write(&mut self) -> wasmtime_wasi::StreamResult<usize> { Ok(1024) }
289}
290
291#[wasmtime_wasi::async_trait]
292impl wasmtime_wasi::Subscribe for InfoStream {
293 async fn ready(&mut self) {}
294}
295
296struct ErrorStream(String);
297
298impl wasmtime_wasi::HostOutputStream for ErrorStream {
299 fn write(&mut self, bytes: bytes::Bytes) -> wasmtime_wasi::StreamResult<()> {
300 tracing::error!("{}: {}", self.0, String::from_utf8_lossy(bytes.as_ref()));
301 Ok(())
302 }
303
304 fn flush(&mut self) -> wasmtime_wasi::StreamResult<()> { Ok(()) }
305
306 fn check_write(&mut self) -> wasmtime_wasi::StreamResult<usize> { Ok(1024) }
307}
308
309#[wasmtime_wasi::async_trait]
310impl wasmtime_wasi::Subscribe for ErrorStream {
311 async fn ready(&mut self) {}
312}
313
314struct LogStream(String, tracing::Level);
315
316impl wasmtime_wasi::StdoutStream for LogStream {
317 fn stream(&self) -> Box<dyn wasmtime_wasi::HostOutputStream> {
318 if self.1 == tracing::Level::INFO {
319 Box::new(InfoStream(self.0.clone()))
320 } else {
321 Box::new(ErrorStream(self.0.clone()))
322 }
323 }
324
325 fn isatty(&self) -> bool { true }
326}
327
328impl PluginModule {
329 pub fn new(name: String, wasm_data: &[u8]) -> Result<Self, PluginModuleError> {
331 let ecs = Arc::new(EcsAccessManager::default());
332
333 let mut config = Config::new();
335 config.wasm_component_model(true);
336
337 let engine = Engine::new(&config).map_err(PluginModuleError::Wasmtime)?;
338 let wasi = wasmtime_wasi::WasiCtxBuilder::new()
340 .stdout(LogStream(name.clone(), tracing::Level::INFO))
341 .stderr(LogStream(name.clone(), tracing::Level::ERROR))
342 .build();
343 let host_ctx = WasiHostCtx {
344 preview2_ctx: wasi,
345 preview2_table: wasmtime_wasi::ResourceTable::new(),
346 ecs: Arc::clone(&ecs),
347 registered_commands: HashSet::new(),
348 registered_bodies: HashMap::new(),
349 };
350 let mut store = Store::new(&engine, host_ctx);
352
353 let module =
355 Component::from_binary(&engine, wasm_data).map_err(PluginModuleError::Wasmtime)?;
356
357 let mut linker = Linker::new(&engine);
359 wasmtime_wasi::add_to_linker_sync(&mut linker).map_err(PluginModuleError::Wasmtime)?;
360 Plugin::add_to_linker(&mut linker, |x| x).map_err(PluginModuleError::Wasmtime)?;
361
362 let instance_fut = linker.instantiate(&mut store, &module);
363 let instance = (instance_fut).map_err(PluginModuleError::Wasmtime)?;
364
365 let plugin = match Plugin::new(&mut store, &instance) {
366 Ok(pl) => Ok(PluginWrapper::Full(pl)),
367 Err(_) => match animation_plugin::AnimationPlugin::new(&mut store, &instance) {
368 Ok(pl) => Ok(PluginWrapper::Animation(pl)),
369 Err(_) => server_plugin::ServerPlugin::new(&mut store, &instance)
370 .map(PluginWrapper::Server),
371 },
372 }
373 .map_err(PluginModuleError::Wasmtime)?;
374
375 Ok(Self {
376 plugin,
377 ecs,
378 store: store.into(),
379 name,
380 })
381 }
382
383 pub fn name(&self) -> &str { &self.name }
384
385 pub fn load_event(
387 &mut self,
388 ecs: &EcsWorld,
389 mode: common::resources::GameMode,
390 ) -> Result<(), PluginModuleError> {
391 self.ecs
392 .execute_with(ecs, || {
393 self.plugin.load_event(self.store.get_mut().unwrap(), mode)
394 })
395 .map_err(PluginModuleError::Wasmtime)
396 }
397
398 pub fn command_event(
399 &mut self,
400 ecs: &EcsWorld,
401 name: &str,
402 args: &[String],
403 player: common::uid::Uid,
404 ) -> Result<Vec<String>, CommandResults> {
405 if !self
406 .store
407 .get_mut()
408 .unwrap()
409 .data()
410 .registered_commands
411 .contains(name)
412 {
413 return Err(CommandResults::UnknownCommand);
414 }
415 self.ecs.execute_with(ecs, || {
416 match self
417 .plugin
418 .command_event(self.store.get_mut().unwrap(), name, args, player.0)
419 {
420 Err(err) => Err(CommandResults::HostError(err)),
421 Ok(result) => result.map_err(CommandResults::PluginError),
422 }
423 })
424 }
425
426 pub fn player_join_event(
427 &mut self,
428 ecs: &EcsWorld,
429 name: &str,
430 uuid: common::uuid::Uuid,
431 ) -> types::JoinResult {
432 self.ecs.execute_with(ecs, || {
433 match self.plugin.player_join_event(
434 self.store.get_mut().unwrap(),
435 name,
436 uuid.as_u64_pair(),
437 ) {
438 Ok(value) => {
439 tracing::info!("JoinResult {value:?}");
440 value
441 },
442 Err(err) => {
443 tracing::error!("join_event: {err:?}");
444 types::JoinResult::None
445 },
446 }
447 })
448 }
449
450 pub fn create_body(&mut self, bodytype: &str) -> Option<animation::Body> {
451 let store = self.store.get_mut().unwrap();
452 let bodytype = store.data().registered_bodies.get(bodytype).copied();
453 bodytype.and_then(|bd| self.plugin.create_body(store, bd))
454 }
455
456 pub fn update_skeleton(
457 &mut self,
458 body: &animation::Body,
459 dep: &types::Dependency,
460 time: f32,
461 ) -> Option<types::Skeleton> {
462 self.plugin
463 .update_skeleton(self.store.get_mut().unwrap(), *body, *dep, time)
464 }
465}