1use crate::event::ScriptErrorEvent;
6use asset::{
7 configure_asset_systems, configure_asset_systems_for_plugin, AssetPathToLanguageMapper,
8 Language, ScriptAsset, ScriptAssetLoader, ScriptAssetSettings,
9};
10use bevy::prelude::*;
11use bindings::{
12 function::script_function::AppScriptFunctionRegistry,
13 garbage_collector,
14 globals::{core::CoreScriptGlobalsPlugin, AppScriptGlobalsRegistry},
15 schedule::AppScheduleRegistry,
16 script_value::ScriptValue,
17 AppReflectAllocator, ReflectAllocator, ReflectReference, ScriptTypeRegistration,
18};
19use commands::{AddStaticScript, RemoveStaticScript};
20use context::{
21 Context, ContextAssigner, ContextBuilder, ContextInitializer, ContextLoadingSettings,
22 ContextPreHandlingInitializer, ScriptContexts,
23};
24use error::ScriptError;
25use event::ScriptCallbackEvent;
26use handler::{CallbackSettings, HandlerFn};
27use runtime::{initialize_runtime, Runtime, RuntimeContainer, RuntimeInitializer, RuntimeSettings};
28use script::{ScriptId, Scripts, StaticScripts};
29
30pub mod asset;
31pub mod bindings;
32pub mod commands;
33pub mod context;
34pub mod docgen;
35pub mod error;
36pub mod event;
37pub mod extractors;
38pub mod handler;
39pub mod reflection_extensions;
40pub mod runtime;
41pub mod script;
42
43#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
44pub enum ScriptingSystemSet {
46 ScriptAssetDispatch,
48 ScriptCommandDispatch,
50 ScriptMetadataRemoval,
52
53 RuntimeInitialization,
55
56 GarbageCollection,
58}
59
60pub trait IntoScriptPluginParams: 'static {
67 const LANGUAGE: Language;
69 type C: Context;
71 type R: Runtime;
73
74 fn build_runtime() -> Self::R;
76}
77
78pub struct ScriptingPlugin<P: IntoScriptPluginParams> {
80 pub runtime_settings: RuntimeSettings<P>,
82 pub callback_handler: HandlerFn<P>,
84 pub context_builder: ContextBuilder<P>,
86 pub context_assigner: ContextAssigner<P>,
88
89 pub language_mapper: AssetPathToLanguageMapper,
91
92 pub context_initializers: Vec<ContextInitializer<P>>,
94 pub context_pre_handling_initializers: Vec<ContextPreHandlingInitializer<P>>,
96
97 pub supported_extensions: &'static [&'static str],
99}
100
101impl<P: IntoScriptPluginParams> Default for ScriptingPlugin<P> {
102 fn default() -> Self {
103 Self {
104 runtime_settings: Default::default(),
105 callback_handler: CallbackSettings::<P>::default().callback_handler,
106 context_builder: Default::default(),
107 context_assigner: Default::default(),
108 language_mapper: Default::default(),
109 context_initializers: Default::default(),
110 context_pre_handling_initializers: Default::default(),
111 supported_extensions: Default::default(),
112 }
113 }
114}
115
116impl<P: IntoScriptPluginParams> Plugin for ScriptingPlugin<P> {
117 fn build(&self, app: &mut bevy::prelude::App) {
118 app.insert_resource(self.runtime_settings.clone())
119 .insert_resource::<RuntimeContainer<P>>(RuntimeContainer {
120 runtime: P::build_runtime(),
121 })
122 .init_resource::<ScriptContexts<P>>()
123 .insert_resource::<CallbackSettings<P>>(CallbackSettings {
124 callback_handler: self.callback_handler,
125 })
126 .insert_resource::<ContextLoadingSettings<P>>(ContextLoadingSettings {
127 loader: self.context_builder.clone(),
128 assigner: self.context_assigner.clone(),
129 context_initializers: self.context_initializers.clone(),
130 context_pre_handling_initializers: self.context_pre_handling_initializers.clone(),
131 });
132
133 register_script_plugin_systems::<P>(app);
134
135 once_per_app_init(app);
137
138 app.add_supported_script_extensions(self.supported_extensions);
139
140 app.world_mut()
141 .resource_mut::<ScriptAssetSettings>()
142 .as_mut()
143 .script_language_mappers
144 .push(self.language_mapper);
145
146 register_types(app);
147 }
148
149 fn finish(&self, app: &mut App) {
150 once_per_app_finalize(app);
151 }
152}
153
154impl<P: IntoScriptPluginParams> ScriptingPlugin<P> {
155 pub fn add_context_initializer(&mut self, initializer: ContextInitializer<P>) -> &mut Self {
159 self.context_initializers.push(initializer);
160 self
161 }
162
163 pub fn add_context_pre_handling_initializer(
167 &mut self,
168 initializer: ContextPreHandlingInitializer<P>,
169 ) -> &mut Self {
170 self.context_pre_handling_initializers.push(initializer);
171 self
172 }
173
174 pub fn add_runtime_initializer(&mut self, initializer: RuntimeInitializer<P>) -> &mut Self {
178 self.runtime_settings.initializers.push(initializer);
179 self
180 }
181}
182
183pub trait ConfigureScriptPlugin {
185 type P: IntoScriptPluginParams;
187
188 fn add_context_initializer(self, initializer: ContextInitializer<Self::P>) -> Self;
190
191 fn add_context_pre_handling_initializer(
193 self,
194 initializer: ContextPreHandlingInitializer<Self::P>,
195 ) -> Self;
196
197 fn add_runtime_initializer(self, initializer: RuntimeInitializer<Self::P>) -> Self;
199
200 fn enable_context_sharing(self) -> Self;
205}
206
207impl<P: IntoScriptPluginParams + AsMut<ScriptingPlugin<P>>> ConfigureScriptPlugin for P {
208 type P = P;
209
210 fn add_context_initializer(mut self, initializer: ContextInitializer<Self::P>) -> Self {
211 self.as_mut().add_context_initializer(initializer);
212 self
213 }
214
215 fn add_context_pre_handling_initializer(
216 mut self,
217 initializer: ContextPreHandlingInitializer<Self::P>,
218 ) -> Self {
219 self.as_mut()
220 .add_context_pre_handling_initializer(initializer);
221 self
222 }
223
224 fn add_runtime_initializer(mut self, initializer: RuntimeInitializer<Self::P>) -> Self {
225 self.as_mut().add_runtime_initializer(initializer);
226 self
227 }
228
229 fn enable_context_sharing(mut self) -> Self {
230 self.as_mut().context_assigner = ContextAssigner::new_global_context_assigner();
231 self
232 }
233}
234
235fn once_per_app_finalize(app: &mut App) {
236 #[derive(Resource)]
237 struct BMSFinalized;
238
239 if app.world().contains_resource::<BMSFinalized>() {
240 return;
241 }
242 app.insert_resource(BMSFinalized);
243
244 let asset_settings_extensions = app
246 .world_mut()
247 .get_resource_or_init::<ScriptAssetSettings>()
248 .supported_extensions;
249
250 bevy::log::info!(
252 "Initializing BMS with Supported extensions: {:?}",
253 asset_settings_extensions
254 );
255
256 app.register_asset_loader(ScriptAssetLoader {
257 extensions: asset_settings_extensions,
258 preprocessor: None,
259 });
260
261 pre_register_componnents(app);
263}
264
265fn pre_register_componnents(app: &mut App) {
267 let type_registry = app
268 .world_mut()
269 .get_resource_or_init::<AppTypeRegistry>()
270 .clone();
271 let type_registry = type_registry.read();
272
273 let world = app.world_mut();
274 for (_, data) in type_registry.iter_with_data::<ReflectComponent>() {
275 data.register_component(world);
276 }
277}
278
279fn once_per_app_init(app: &mut App) {
281 #[derive(Resource)]
282 struct BMSInitialized;
283
284 if app.world().contains_resource::<BMSInitialized>() {
285 return;
286 }
287 app.insert_resource(BMSInitialized);
288
289 app.add_event::<ScriptErrorEvent>()
290 .add_event::<ScriptCallbackEvent>()
291 .init_resource::<AppReflectAllocator>()
292 .init_resource::<Scripts>()
293 .init_resource::<StaticScripts>()
294 .init_asset::<ScriptAsset>()
295 .init_resource::<AppScriptFunctionRegistry>()
296 .init_resource::<AppScriptGlobalsRegistry>()
297 .insert_resource(AppScheduleRegistry::new());
298
299 app.add_systems(
300 PostUpdate,
301 ((garbage_collector).in_set(ScriptingSystemSet::GarbageCollection),),
302 );
303
304 app.add_plugins(CoreScriptGlobalsPlugin);
305
306 configure_asset_systems(app);
307}
308
309fn register_script_plugin_systems<P: IntoScriptPluginParams>(app: &mut App) {
311 app.add_systems(
312 PostStartup,
313 (initialize_runtime::<P>.pipe(|e: In<Result<(), ScriptError>>| {
314 if let Err(e) = e.0 {
315 error!("Error initializing runtime: {:?}", e);
316 }
317 }))
318 .in_set(ScriptingSystemSet::RuntimeInitialization),
319 );
320
321 configure_asset_systems_for_plugin::<P>(app);
322}
323
324fn register_types(app: &mut App) {
326 app.register_type::<ScriptValue>();
327 app.register_type::<ScriptTypeRegistration>();
328 app.register_type::<ReflectReference>();
329}
330
331pub trait AddRuntimeInitializer {
333 fn add_runtime_initializer<P: IntoScriptPluginParams>(
335 &mut self,
336 initializer: RuntimeInitializer<P>,
337 ) -> &mut Self;
338}
339
340impl AddRuntimeInitializer for App {
341 fn add_runtime_initializer<P: IntoScriptPluginParams>(
342 &mut self,
343 initializer: RuntimeInitializer<P>,
344 ) -> &mut Self {
345 if !self.world_mut().contains_resource::<RuntimeSettings<P>>() {
346 self.world_mut().init_resource::<RuntimeSettings<P>>();
347 }
348 self.world_mut()
349 .resource_mut::<RuntimeSettings<P>>()
350 .as_mut()
351 .initializers
352 .push(initializer);
353 self
354 }
355}
356
357pub trait ManageStaticScripts {
359 fn add_static_script(&mut self, script_id: impl Into<ScriptId>) -> &mut Self;
363
364 fn remove_static_script(&mut self, script_id: impl Into<ScriptId>) -> &mut Self;
368}
369
370impl ManageStaticScripts for App {
371 fn add_static_script(&mut self, script_id: impl Into<ScriptId>) -> &mut Self {
372 AddStaticScript::new(script_id.into()).apply(self.world_mut());
373 self
374 }
375
376 fn remove_static_script(&mut self, script_id: impl Into<ScriptId>) -> &mut Self {
377 RemoveStaticScript::new(script_id.into()).apply(self.world_mut());
378 self
379 }
380}
381
382pub trait ConfigureScriptAssetSettings {
387 fn add_supported_script_extensions(&mut self, extensions: &[&'static str]) -> &mut Self;
389}
390
391impl ConfigureScriptAssetSettings for App {
392 fn add_supported_script_extensions(&mut self, extensions: &[&'static str]) -> &mut Self {
393 let mut asset_settings = self
394 .world_mut()
395 .get_resource_or_init::<ScriptAssetSettings>();
396
397 let mut new_arr = Vec::from(asset_settings.supported_extensions);
398
399 new_arr.extend(extensions);
400
401 let new_arr_static = Vec::leak(new_arr);
402
403 asset_settings.supported_extensions = new_arr_static;
404
405 self
406 }
407}
408
409#[cfg(test)]
410mod test {
411 use super::*;
412
413 #[tokio::test]
414 async fn test_asset_extensions_correctly_accumulate() {
415 let mut app = App::new();
416 app.init_resource::<ScriptAssetSettings>();
417 app.add_plugins(AssetPlugin::default());
418
419 app.world_mut()
420 .resource_mut::<ScriptAssetSettings>()
421 .supported_extensions = &["lua", "rhai"];
422
423 once_per_app_finalize(&mut app);
424
425 let asset_loader = app
426 .world()
427 .get_resource::<AssetServer>()
428 .expect("Asset loader not found");
429
430 asset_loader
431 .get_asset_loader_with_extension("lua")
432 .await
433 .expect("Lua loader not found");
434
435 asset_loader
436 .get_asset_loader_with_extension("rhai")
437 .await
438 .expect("Rhai loader not found");
439 }
440
441 #[test]
442 fn test_reflect_component_is_preregistered_in_app_finalize() {
443 let mut app = App::new();
444
445 app.add_plugins(AssetPlugin::default());
446
447 #[derive(Component, Reflect)]
448 #[reflect(Component)]
449 struct Comp;
450
451 app.register_type::<Comp>();
452
453 assert!(app.world_mut().component_id::<Comp>().is_none());
454
455 once_per_app_finalize(&mut app);
456
457 assert!(app.world_mut().component_id::<Comp>().is_some());
458 }
459}