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 let world = unsafe { self.ecs.get().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 fn name(
246 &mut self,
247 self_: wasmtime::component::Resource<information::Entity>,
248 ) -> Result<String, types::Error> {
249 let uid = self
250 .table()
251 .get(&self_)
252 .map_err(|_err| types::Error::RuntimeError)?
253 .uid;
254 let world = unsafe { self.ecs.get().ok_or(types::Error::EcsPointerNotAvailable)? };
256 let player = world
257 .id_maps
258 .uid_entity(uid)
259 .ok_or(types::Error::EcsEntityNotFound)?;
260 Ok(world
261 .player
262 .get(player)
263 .ok_or(types::Error::EcsComponentNotFound)?
264 .alias
265 .to_owned())
266 }
267
268 fn drop(
269 &mut self,
270 rep: wasmtime::component::Resource<information::Entity>,
271 ) -> wasmtime::Result<()> {
272 Ok(self.table().delete(rep).map(|_entity| ())?)
273 }
274}
275
276struct InfoStream(String);
277
278impl wasmtime_wasi::HostOutputStream for InfoStream {
279 fn write(&mut self, bytes: bytes::Bytes) -> wasmtime_wasi::StreamResult<()> {
280 tracing::info!("{}: {}", self.0, String::from_utf8_lossy(bytes.as_ref()));
281 Ok(())
282 }
283
284 fn flush(&mut self) -> wasmtime_wasi::StreamResult<()> { Ok(()) }
285
286 fn check_write(&mut self) -> wasmtime_wasi::StreamResult<usize> { Ok(1024) }
287}
288
289#[wasmtime_wasi::async_trait]
290impl wasmtime_wasi::Subscribe for InfoStream {
291 async fn ready(&mut self) {}
292}
293
294struct ErrorStream(String);
295
296impl wasmtime_wasi::HostOutputStream for ErrorStream {
297 fn write(&mut self, bytes: bytes::Bytes) -> wasmtime_wasi::StreamResult<()> {
298 tracing::error!("{}: {}", self.0, String::from_utf8_lossy(bytes.as_ref()));
299 Ok(())
300 }
301
302 fn flush(&mut self) -> wasmtime_wasi::StreamResult<()> { Ok(()) }
303
304 fn check_write(&mut self) -> wasmtime_wasi::StreamResult<usize> { Ok(1024) }
305}
306
307#[wasmtime_wasi::async_trait]
308impl wasmtime_wasi::Subscribe for ErrorStream {
309 async fn ready(&mut self) {}
310}
311
312struct LogStream(String, tracing::Level);
313
314impl wasmtime_wasi::StdoutStream for LogStream {
315 fn stream(&self) -> Box<dyn wasmtime_wasi::HostOutputStream> {
316 if self.1 == tracing::Level::INFO {
317 Box::new(InfoStream(self.0.clone()))
318 } else {
319 Box::new(ErrorStream(self.0.clone()))
320 }
321 }
322
323 fn isatty(&self) -> bool { true }
324}
325
326impl PluginModule {
327 pub fn new(name: String, wasm_data: &[u8]) -> Result<Self, PluginModuleError> {
329 let ecs = Arc::new(EcsAccessManager::default());
330
331 let mut config = Config::new();
333 config.wasm_component_model(true);
334
335 let engine = Engine::new(&config).map_err(PluginModuleError::Wasmtime)?;
336 let wasi = wasmtime_wasi::WasiCtxBuilder::new()
338 .stdout(LogStream(name.clone(), tracing::Level::INFO))
339 .stderr(LogStream(name.clone(), tracing::Level::ERROR))
340 .build();
341 let host_ctx = WasiHostCtx {
342 preview2_ctx: wasi,
343 preview2_table: wasmtime_wasi::ResourceTable::new(),
344 ecs: Arc::clone(&ecs),
345 registered_commands: HashSet::new(),
346 registered_bodies: HashMap::new(),
347 };
348 let mut store = Store::new(&engine, host_ctx);
350
351 let module =
353 Component::from_binary(&engine, wasm_data).map_err(PluginModuleError::Wasmtime)?;
354
355 let mut linker = Linker::new(&engine);
357 wasmtime_wasi::add_to_linker_sync(&mut linker).map_err(PluginModuleError::Wasmtime)?;
358 Plugin::add_to_linker(&mut linker, |x| x).map_err(PluginModuleError::Wasmtime)?;
359
360 let instance_fut = linker.instantiate(&mut store, &module);
361 let instance = (instance_fut).map_err(PluginModuleError::Wasmtime)?;
362
363 let plugin = match Plugin::new(&mut store, &instance) {
364 Ok(pl) => Ok(PluginWrapper::Full(pl)),
365 Err(_) => match animation_plugin::AnimationPlugin::new(&mut store, &instance) {
366 Ok(pl) => Ok(PluginWrapper::Animation(pl)),
367 Err(_) => server_plugin::ServerPlugin::new(&mut store, &instance)
368 .map(PluginWrapper::Server),
369 },
370 }
371 .map_err(PluginModuleError::Wasmtime)?;
372
373 Ok(Self {
374 plugin,
375 ecs,
376 store: store.into(),
377 name,
378 })
379 }
380
381 pub fn name(&self) -> &str { &self.name }
382
383 pub fn load_event(
385 &mut self,
386 ecs: &EcsWorld,
387 mode: common::resources::GameMode,
388 ) -> Result<(), PluginModuleError> {
389 self.ecs
390 .execute_with(ecs, || {
391 self.plugin.load_event(self.store.get_mut().unwrap(), mode)
392 })
393 .map_err(PluginModuleError::Wasmtime)
394 }
395
396 pub fn command_event(
397 &mut self,
398 ecs: &EcsWorld,
399 name: &str,
400 args: &[String],
401 player: common::uid::Uid,
402 ) -> Result<Vec<String>, CommandResults> {
403 if !self
404 .store
405 .get_mut()
406 .unwrap()
407 .data()
408 .registered_commands
409 .contains(name)
410 {
411 return Err(CommandResults::UnknownCommand);
412 }
413 self.ecs.execute_with(ecs, || {
414 match self
415 .plugin
416 .command_event(self.store.get_mut().unwrap(), name, args, player.0)
417 {
418 Err(err) => Err(CommandResults::HostError(err)),
419 Ok(result) => result.map_err(CommandResults::PluginError),
420 }
421 })
422 }
423
424 pub fn player_join_event(
425 &mut self,
426 ecs: &EcsWorld,
427 name: &str,
428 uuid: common::uuid::Uuid,
429 ) -> types::JoinResult {
430 self.ecs.execute_with(ecs, || {
431 match self.plugin.player_join_event(
432 self.store.get_mut().unwrap(),
433 name,
434 uuid.as_u64_pair(),
435 ) {
436 Ok(value) => {
437 tracing::info!("JoinResult {value:?}");
438 value
439 },
440 Err(err) => {
441 tracing::error!("join_event: {err:?}");
442 types::JoinResult::None
443 },
444 }
445 })
446 }
447
448 pub fn create_body(&mut self, bodytype: &str) -> Option<animation::Body> {
449 let store = self.store.get_mut().unwrap();
450 let bodytype = store.data().registered_bodies.get(bodytype).copied();
451 bodytype.and_then(|bd| self.plugin.create_body(store, bd))
452 }
453
454 pub fn update_skeleton(
455 &mut self,
456 body: &animation::Body,
457 dep: &types::Dependency,
458 time: f32,
459 ) -> Option<types::Skeleton> {
460 self.plugin
461 .update_skeleton(self.store.get_mut().unwrap(), *body, *dep, time)
462 }
463}