1use std::{marker::PhantomData, sync::Arc};
2
3use bevy::prelude::*;
4use bevy_mod_scripting_core::{
5 prelude::*,
6 systems::{self, CachedScriptState},
7 world::{WorldPointer, WorldPointerGuard},
8};
9use prelude::{RuneDocFragment, RuneFile, RuneLoader};
10use rune::{
11 runtime::{Args, RuntimeContext, VmError, VmResult},
12 Context, Diagnostics, Source, Sources, Unit, Vm,
13};
14
15mod assets;
16mod docs;
17
18pub mod prelude {
19 pub use crate::{
20 assets::{RuneFile, RuneLoader},
21 docs::RuneDocFragment,
22 RuneArgs, RuneEvent, RuneScriptContext, RuneScriptHost,
23 };
24 pub use rune::{self, runtime::Args, Context};
25}
26
27pub trait RuneArgs: Args + Clone + Send + Sync + 'static {}
31
32impl<T: Args + Clone + Send + Sync + 'static> RuneArgs for T {}
33
34#[derive(Debug, Clone, Event)]
36pub struct RuneEvent<A: RuneArgs> {
37 pub hook_name: String,
39 pub args: A,
42 pub recipients: Recipients,
44}
45
46impl<A: RuneArgs> ScriptEvent for RuneEvent<A> {
47 fn recipients(&self) -> &Recipients {
48 &self.recipients
49 }
50}
51
52struct RuneVm(Vm);
54
55impl Default for RuneVm {
56 fn default() -> Self {
57 Self(Vm::new(
58 Arc::new(RuntimeContext::default()),
59 Arc::new(Unit::default()),
60 ))
61 }
62}
63
64pub struct RuneScriptContext {
66 pub unit: Arc<Unit>,
67 pub runtime_context: Arc<RuntimeContext>,
68}
69
70#[derive(Resource)]
71pub struct RuneScriptHost<A: RuneArgs> {
73 _ph: PhantomData<A>,
74}
75
76impl<A: RuneArgs> Default for RuneScriptHost<A> {
77 fn default() -> Self {
78 Self {
79 _ph: Default::default(),
80 }
81 }
82}
83
84impl<A: RuneArgs> RuneScriptHost<A> {
85 #[cold]
88 fn handle_rune_error(world: WorldPointer, error: VmError, script_data: &ScriptData<'_>) {
89 let mut world = world.write();
90 let mut state: CachedScriptState<Self> = world.remove_resource().unwrap();
91
92 let (_, mut error_wrt, _) = state.event_state.get_mut(&mut world);
93
94 let error = ScriptError::RuntimeError {
95 script: script_data.name.to_owned(),
96 msg: error.to_string(),
97 };
98
99 error!("{}", error);
100
101 error_wrt.send(ScriptErrorEvent { error });
102 world.insert_resource(state);
103 }
104}
105
106impl<A: RuneArgs> ScriptHost for RuneScriptHost<A> {
107 type ScriptContext = RuneScriptContext;
108
109 type ScriptEvent = RuneEvent<A>;
110
111 type ScriptAsset = RuneFile;
112
113 type APITarget = Context;
114
115 type DocTarget = RuneDocFragment;
116
117 fn register_with_app_in_set(
118 app: &mut App,
119 schedule: impl bevy::ecs::schedule::ScheduleLabel,
120 set: impl SystemSet,
121 ) {
122 app.add_priority_event::<Self::ScriptEvent>()
123 .init_asset::<RuneFile>()
124 .init_asset_loader::<RuneLoader>()
125 .init_resource::<CachedScriptState<Self>>()
126 .init_resource::<ScriptContexts<Self::ScriptContext>>()
127 .init_resource::<APIProviders<Self>>()
128 .register_type::<ScriptCollection<Self::ScriptAsset>>()
129 .register_type::<Script<Self::ScriptAsset>>()
130 .register_type::<Handle<RuneFile>>()
131 .insert_non_send_resource(RuneVm::default())
133 .add_systems(
136 schedule,
137 (
138 systems::script_add_synchronizer::<Self>,
139 systems::script_remove_synchronizer::<Self>,
140 systems::script_hot_reload_handler::<Self>,
141 )
142 .chain()
143 .in_set(set),
144 );
145 }
146
147 fn load_script(
148 &mut self,
149 script: &[u8],
150 script_data: &ScriptData,
151 providers: &mut APIProviders<Self>,
152 ) -> Result<Self::ScriptContext, ScriptError> {
153 let mut context = rune_modules::default_context().map_err(ScriptError::new_other)?;
154
155 providers.attach_all(&mut context).unwrap();
158
159 let mut diagnostics = Diagnostics::new();
160
161 let mut sources = Sources::new();
162 sources
163 .insert(
164 Source::new(
165 script_data.name,
166 std::str::from_utf8(script).expect("Slice is not UTF-8"),
167 )
168 .map_err(|msg| ScriptError::FailedToLoad {
169 script: script_data.name.into(),
170 msg: msg.to_string(),
171 })?,
172 )
173 .map_err(|msg| ScriptError::FailedToLoad {
174 script: script_data.name.into(),
175 msg: msg.to_string(),
176 })?;
177
178 let result = rune::prepare(&mut sources)
179 .with_context(&context)
180 .with_diagnostics(&mut diagnostics)
181 .build();
182
183 if !diagnostics.is_empty() {
184 let mut writer = rune::termcolor::Buffer::no_color();
185
186 diagnostics
187 .emit(&mut writer, &sources)
188 .expect("Failed to write diagnostics to buffer");
189
190 return Err(ScriptError::SyntaxError {
191 script: script_data.name.into(),
192 msg: std::str::from_utf8(writer.as_slice())
193 .expect("Slice was not UTF-8")
194 .to_owned(),
195 });
196 }
197
198 let unit = result.expect("Failed to build Rune unit.");
199
200 let runtime_ctx = context
201 .runtime()
202 .expect("Failed to create Rune runtime context.");
203
204 Ok(RuneScriptContext {
205 unit: Arc::new(unit),
206 runtime_context: Arc::new(runtime_ctx),
207 })
208 }
209
210 fn setup_script(
211 &mut self,
212 script_data: &ScriptData,
213 ctx: &mut Self::ScriptContext,
214 providers: &mut APIProviders<Self>,
215 ) -> Result<(), ScriptError> {
216 providers.setup_all(script_data, ctx)
217 }
218
219 fn handle_events<'a>(
220 &mut self,
221 world: &mut World,
222 events: &[Self::ScriptEvent],
223 ctxs: impl Iterator<Item = (ScriptData<'a>, &'a mut Self::ScriptContext)>,
224 providers: &mut APIProviders<Self>,
225 ) {
226 let RuneVm(mut vm) = world.remove_non_send_resource::<RuneVm>().unwrap();
228
229 {
230 let world = unsafe { WorldPointerGuard::new(world) };
235
236 ctxs.for_each(|(script_data, ctx)| {
237 providers
238 .setup_runtime_all(world.clone(), &script_data, ctx)
239 .expect("Could not setup script runtime");
240
241 for event in events {
242 if !event.recipients().is_recipient(&script_data) {
243 continue;
244 }
245
246 *vm.context_mut() = Arc::clone(&ctx.runtime_context);
248 *vm.unit_mut() = Arc::clone(&ctx.unit);
249
250 let mut exec = match vm.execute([event.hook_name.as_str()], event.args.clone())
251 {
252 Ok(exec) => exec,
253 Err(error) => {
254 Self::handle_rune_error(world.clone(), error, &script_data);
255 continue;
256 }
257 };
258
259 if let VmResult::Err(error) = exec.complete() {
260 Self::handle_rune_error(world.clone(), error, &script_data);
261 }
262 }
263 });
264
265 drop(world);
267 }
268
269 world.insert_non_send_resource(RuneVm(vm));
270 }
271}