use crate::{
callbacks::ScriptCallbacksPlugin,
config::{GetPluginThreadConfig, ScriptingPluginConfiguration},
context::{ContextLoadFn, ContextReloadFn},
event::ScriptErrorEvent,
handler::script_error_logger,
pipeline::ScriptLoadingPipeline,
};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{AssetApp, Handle};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::{
reflect::{AppTypeRegistry, ReflectComponent},
schedule::SystemSet,
};
use bevy_log::error;
use bevy_mod_scripting_asset::{Language, LanguageExtensions, ScriptAsset, ScriptAssetLoader};
use bevy_mod_scripting_bindings::{
AppReflectAllocator, AppScheduleRegistry, AppScriptFunctionRegistry,
DummyScriptFunctionRegistry, DynamicScriptComponentPlugin, MarkAsCore, ReflectReference,
ScriptTypeRegistration, ScriptValue, ThreadWorldContainer, garbage_collector,
};
use context::{Context, ContextInitializer, ContextPreHandlingInitializer};
use event::{ScriptCallbackEvent, ScriptCallbackResponseEvent};
use handler::HandlerFn;
use runtime::{Runtime, RuntimeInitializer};
use script::{ContextPolicy, ScriptComponent, ScriptContext};
pub mod callbacks;
pub mod commands;
pub mod config;
pub mod context;
pub mod error;
pub mod event;
pub mod extractors;
pub mod handler;
pub mod pipeline;
pub mod runtime;
pub mod script;
pub mod script_system;
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
pub enum ScriptingSystemSet {
ScriptAssetDispatch,
ScriptCommandDispatch,
RuntimeInitialization,
GarbageCollection,
}
pub trait IntoScriptPluginParams: 'static + GetPluginThreadConfig<Self> {
const LANGUAGE: Language;
type C: Context;
type R: Runtime;
fn build_runtime() -> Self::R;
fn handler() -> HandlerFn<Self>;
fn context_loader() -> ContextLoadFn<Self>;
fn context_reloader() -> ContextReloadFn<Self>;
}
pub struct ScriptingPlugin<P: IntoScriptPluginParams> {
pub runtime_initializers: Vec<RuntimeInitializer<P>>,
pub context_policy: ContextPolicy,
pub language: Language,
pub supported_extensions: Vec<&'static str>,
pub context_initializers: Vec<ContextInitializer<P>>,
pub context_pre_handling_initializers: Vec<ContextPreHandlingInitializer<P>>,
pub emit_responses: bool,
pub processing_pipeline_plugin: ScriptLoadingPipeline<P>,
}
impl<P> std::fmt::Debug for ScriptingPlugin<P>
where
P: IntoScriptPluginParams,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScriptingPlugin")
.field("context_policy", &self.context_policy)
.field("language", &self.language)
.field("context_initializers", &self.context_initializers)
.field(
"context_pre_handling_initializers",
&self.context_pre_handling_initializers,
)
.field("emit_responses", &self.emit_responses)
.finish()
}
}
impl<P: IntoScriptPluginParams> Default for ScriptingPlugin<P> {
fn default() -> Self {
Self {
runtime_initializers: Default::default(),
context_policy: ContextPolicy::default(),
language: Default::default(),
supported_extensions: Default::default(),
context_initializers: Default::default(),
context_pre_handling_initializers: Default::default(),
emit_responses: false,
processing_pipeline_plugin: Default::default(),
}
}
}
#[profiling::all_functions]
impl<P: IntoScriptPluginParams> Plugin for ScriptingPlugin<P> {
fn build(&self, app: &mut App) {
let runtime = P::build_runtime();
for initializer in &self.runtime_initializers {
if let Err(e) = initializer(&runtime) {
error!("Error initializing runtime: {:?}. Continuing.", e);
}
}
let config = ScriptingPluginConfiguration::<P> {
pre_handling_callbacks: Vec::leak(self.context_pre_handling_initializers.clone()),
context_initialization_callbacks: Vec::leak(self.context_initializers.clone()),
emit_responses: self.emit_responses,
runtime: Box::leak(Box::new(runtime)),
language_extensions: Box::leak(Box::new(LanguageExtensions::new(
self.supported_extensions
.iter()
.map(|&ext| (ext, P::LANGUAGE.clone())),
))),
};
P::set_world_local_config(app.world().id(), config);
app.insert_resource(ScriptContext::<P>::new(self.context_policy.clone()));
app.register_asset_loader(ScriptAssetLoader::new(config.language_extensions));
app.add_plugins((
self.processing_pipeline_plugin.clone(),
ScriptCallbacksPlugin::<P>::default(),
));
register_types(app);
}
}
impl<P: IntoScriptPluginParams> ScriptingPlugin<P> {
pub fn add_context_initializer(&mut self, initializer: ContextInitializer<P>) -> &mut Self {
self.context_initializers.push(initializer);
self
}
pub fn add_context_pre_handling_initializer(
&mut self,
initializer: ContextPreHandlingInitializer<P>,
) -> &mut Self {
self.context_pre_handling_initializers.push(initializer);
self
}
pub fn add_runtime_initializer(&mut self, initializer: RuntimeInitializer<P>) -> &mut Self {
self.runtime_initializers.push(initializer);
self
}
pub fn set_pipeline_settings(&mut self, pipeline: ScriptLoadingPipeline<P>) -> &mut Self {
self.processing_pipeline_plugin = pipeline;
self
}
}
pub trait ConfigureScriptPlugin {
type P: IntoScriptPluginParams;
fn add_context_initializer(self, initializer: ContextInitializer<Self::P>) -> Self;
fn add_context_pre_handling_initializer(
self,
initializer: ContextPreHandlingInitializer<Self::P>,
) -> Self;
fn add_runtime_initializer(self, initializer: RuntimeInitializer<Self::P>) -> Self;
fn set_context_policy(self, context_policy: ContextPolicy) -> Self;
fn emit_core_callback_responses(self, emit_responses: bool) -> Self;
fn add_supported_extension(self, extension: &'static str) -> Self;
fn remove_supported_extension(self, extension: &'static str) -> Self;
fn set_pipeline_settings(self, pipeline: ScriptLoadingPipeline<Self::P>) -> Self;
}
impl<P: IntoScriptPluginParams + AsMut<ScriptingPlugin<P>>> ConfigureScriptPlugin for P {
type P = P;
fn add_context_initializer(mut self, initializer: ContextInitializer<Self::P>) -> Self {
self.as_mut().add_context_initializer(initializer);
self
}
fn add_context_pre_handling_initializer(
mut self,
initializer: ContextPreHandlingInitializer<Self::P>,
) -> Self {
self.as_mut()
.add_context_pre_handling_initializer(initializer);
self
}
fn add_runtime_initializer(mut self, initializer: RuntimeInitializer<Self::P>) -> Self {
self.as_mut().add_runtime_initializer(initializer);
self
}
fn set_context_policy(mut self, policy: ContextPolicy) -> Self {
self.as_mut().context_policy = policy;
self
}
fn emit_core_callback_responses(mut self, emit_responses: bool) -> Self {
self.as_mut().emit_responses = emit_responses;
self
}
fn add_supported_extension(mut self, extension: &'static str) -> Self {
self.as_mut().supported_extensions.push(extension);
self
}
fn remove_supported_extension(mut self, extension: &'static str) -> Self {
self.as_mut()
.supported_extensions
.retain(|&ext| ext != extension);
self
}
fn set_pipeline_settings(mut self, pipeline: ScriptLoadingPipeline<P>) -> Self {
self.as_mut().set_pipeline_settings(pipeline);
self
}
}
fn pre_register_components(app: &mut App) {
let type_registry = app
.world_mut()
.get_resource_or_init::<AppTypeRegistry>()
.clone();
let type_registry = type_registry.read();
let world = app.world_mut();
for (_, data) in type_registry.iter_with_data::<ReflectComponent>() {
data.register_component(world);
}
}
#[derive(Default)]
pub struct BMSScriptingInfrastructurePlugin {
dont_log_script_event_errors: bool,
}
impl Plugin for BMSScriptingInfrastructurePlugin {
fn build(&self, app: &mut App) {
app.add_message::<ScriptErrorEvent>()
.add_message::<ScriptCallbackEvent>()
.add_message::<ScriptCallbackResponseEvent>()
.init_resource::<AppReflectAllocator>()
.init_asset::<ScriptAsset>()
.init_resource::<AppScriptFunctionRegistry>()
.init_resource::<DummyScriptFunctionRegistry>()
.insert_resource(AppScheduleRegistry::new());
app.register_type::<ScriptAsset>();
app.register_type::<Handle<ScriptAsset>>();
app.register_type::<ReflectReference>();
app.register_type_data::<Handle<ScriptAsset>, MarkAsCore>();
app.add_systems(
PostUpdate,
((garbage_collector).in_set(ScriptingSystemSet::GarbageCollection),),
);
if !self.dont_log_script_event_errors {
app.add_systems(PostUpdate, script_error_logger);
}
let _ = bevy_mod_scripting_display::GLOBAL_TYPE_INFO_PROVIDER
.set(|| Some(&ThreadWorldContainer));
DynamicScriptComponentPlugin.build(app);
}
fn finish(&self, app: &mut App) {
pre_register_components(app);
DynamicScriptComponentPlugin.finish(app);
}
}
fn register_types(app: &mut App) {
app.register_type::<ScriptValue>();
app.register_type::<ScriptTypeRegistration>();
app.register_type::<ReflectReference>();
app.register_type::<ScriptComponent>();
}
#[cfg(test)]
mod test {
use bevy_asset::AssetPlugin;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use super::*;
#[test]
fn test_reflect_component_is_preregistered_in_app_finalize() {
let mut app = App::new();
app.add_plugins(AssetPlugin::default());
#[derive(Component, Reflect)]
#[reflect(Component)]
struct Comp;
app.register_type::<Comp>();
assert!(app.world_mut().component_id::<Comp>().is_none());
BMSScriptingInfrastructurePlugin::default().finish(&mut app);
assert!(app.world_mut().component_id::<Comp>().is_some());
}
}