Skip to content

feat: Add missing luau extension, improve extension configuration options #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 24 additions & 49 deletions crates/bevy_mod_scripting_core/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ use bevy::{
ResMut,
},
reflect::TypePath,
utils::HashMap,
utils::hashbrown::HashMap,
};
use std::borrow::Cow;

/// Represents a scripting language. Languages which compile into another language should use the target language as their language.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Language {
/// The Rhai scripting language
Rhai,
Expand All @@ -32,6 +32,7 @@ pub enum Language {
/// An external scripting language
External(Cow<'static, str>),
/// Set if none of the asset path to language mappers match
#[default]
Unknown,
}

Expand Down Expand Up @@ -110,8 +111,8 @@ impl AssetLoader for ScriptAssetLoader {
pub struct ScriptAssetSettings {
/// Strategy for mapping asset paths to script ids, by default this is the identity function
pub script_id_mapper: AssetPathToScriptIdMapper,
/// Strategies for mapping asset paths to languages
pub script_language_mappers: Vec<AssetPathToLanguageMapper>,
/// Mapping from extension to script language
pub extension_to_language_map: HashMap<&'static str, Language>,

/// The currently supported asset extensions
/// Should be updated by each scripting plugin to include the extensions it supports.
Expand All @@ -123,15 +124,15 @@ pub struct ScriptAssetSettings {
impl ScriptAssetSettings {
/// Selects the language for a given asset path
pub fn select_script_language(&self, path: &AssetPath) -> Language {
for mapper in &self.script_language_mappers {
let language = (mapper.map)(path);
match language {
Language::Unknown => continue,
_ => return language,
}
}

Language::Unknown
let extension = path
.path()
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or_default();
self.extension_to_language_map
.get(extension)
.cloned()
.unwrap_or_default()
}
}

Expand All @@ -141,7 +142,12 @@ impl Default for ScriptAssetSettings {
script_id_mapper: AssetPathToScriptIdMapper {
map: (|path: &AssetPath| path.path().to_string_lossy().into_owned().into()),
},
script_language_mappers: vec![],
extension_to_language_map: HashMap::from_iter(vec![
("lua", Language::Lua),
("luau", Language::Lua),
("rhai", Language::Rhai),
("rn", Language::Rune),
]),
supported_extensions: &[],
}
}
Expand All @@ -154,21 +160,6 @@ pub struct AssetPathToScriptIdMapper {
pub map: fn(&AssetPath) -> ScriptId,
}

#[derive(Clone, Copy)]
/// Strategy for mapping asset paths to languages
pub struct AssetPathToLanguageMapper {
/// The mapping function
pub map: fn(&AssetPath) -> Language,
}

impl Default for AssetPathToLanguageMapper {
fn default() -> Self {
Self {
map: |_| Language::Unknown,
}
}
}

/// A cache of asset id's to their script id's. Necessary since when we drop an asset we won't have the ability to get the path from the asset.
#[derive(Default, Debug, Resource)]
pub struct ScriptMetadataStore {
Expand Down Expand Up @@ -393,26 +384,10 @@ mod tests {
script_id_mapper: AssetPathToScriptIdMapper {
map: |path| path.path().to_string_lossy().into_owned().into(),
},
script_language_mappers: vec![
AssetPathToLanguageMapper {
map: |path| {
if path.path().extension().unwrap() == "lua" {
Language::Lua
} else {
Language::Unknown
}
},
},
AssetPathToLanguageMapper {
map: |path| {
if path.path().extension().unwrap() == "rhai" {
Language::Rhai
} else {
Language::Unknown
}
},
},
],
extension_to_language_map: HashMap::from_iter(vec![
("lua", Language::Lua),
("rhai", Language::Rhai),
]),
}
}

Expand Down
60 changes: 42 additions & 18 deletions crates/bevy_mod_scripting_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use crate::event::ScriptErrorEvent;
use asset::{
configure_asset_systems, configure_asset_systems_for_plugin, AssetPathToLanguageMapper,
Language, ScriptAsset, ScriptAssetLoader, ScriptAssetSettings,
configure_asset_systems, configure_asset_systems_for_plugin, Language, ScriptAsset,
ScriptAssetLoader, ScriptAssetSettings,
};
use bevy::prelude::*;
use bindings::{
Expand Down Expand Up @@ -87,16 +87,16 @@ pub struct ScriptingPlugin<P: IntoScriptPluginParams> {
/// The strategy for assigning contexts to scripts
pub context_assignment_strategy: ContextAssignmentStrategy,

/// The asset path to language mapper for the plugin
pub language_mapper: AssetPathToLanguageMapper,
/// The language this plugin declares
pub language: Language,
/// Supported extensions to be added to the asset settings without the dot
/// By default BMS populates a set of extensions for the languages it supports.
pub additional_supported_extensions: &'static [&'static str],

/// initializers for the contexts, run when loading the script
pub context_initializers: Vec<ContextInitializer<P>>,
/// initializers for the contexts run every time before handling events
pub context_pre_handling_initializers: Vec<ContextPreHandlingInitializer<P>>,

/// Supported extensions to be added to the asset settings without the dot
pub supported_extensions: &'static [&'static str],
}

impl<P: IntoScriptPluginParams> Default for ScriptingPlugin<P> {
Expand All @@ -106,10 +106,10 @@ impl<P: IntoScriptPluginParams> Default for ScriptingPlugin<P> {
callback_handler: CallbackSettings::<P>::default().callback_handler,
context_builder: Default::default(),
context_assignment_strategy: Default::default(),
language_mapper: Default::default(),
language: Default::default(),
context_initializers: Default::default(),
context_pre_handling_initializers: Default::default(),
supported_extensions: Default::default(),
additional_supported_extensions: Default::default(),
}
}
}
Expand All @@ -136,13 +136,12 @@ impl<P: IntoScriptPluginParams> Plugin for ScriptingPlugin<P> {
// add extension for the language to the asset loader
once_per_app_init(app);

app.add_supported_script_extensions(self.supported_extensions);

app.world_mut()
.resource_mut::<ScriptAssetSettings>()
.as_mut()
.script_language_mappers
.push(self.language_mapper);
if !self.additional_supported_extensions.is_empty() {
app.add_supported_script_extensions(
self.additional_supported_extensions,
self.language.clone(),
);
}

register_types(app);
}
Expand Down Expand Up @@ -203,6 +202,11 @@ pub trait ConfigureScriptPlugin {
/// This means that all scripts will share the same context. This is useful for when you want to share data between scripts easilly.
/// Be careful however as this also means that scripts can interfere with each other in unexpected ways! Including overwriting each other's handlers.
fn enable_context_sharing(self) -> Self;

/// Set the set of extensions to be added for the plugin's language.
///
/// This is useful for adding extensions that are not supported by default by BMS.
fn set_additional_supported_extensions(self, extensions: &'static [&'static str]) -> Self;
}

impl<P: IntoScriptPluginParams + AsMut<ScriptingPlugin<P>>> ConfigureScriptPlugin for P {
Expand Down Expand Up @@ -231,6 +235,11 @@ impl<P: IntoScriptPluginParams + AsMut<ScriptingPlugin<P>>> ConfigureScriptPlugi
self.as_mut().context_assignment_strategy = ContextAssignmentStrategy::Global;
self
}

fn set_additional_supported_extensions(mut self, extensions: &'static [&'static str]) -> Self {
self.as_mut().additional_supported_extensions = extensions;
self
}
}

fn once_per_app_finalize(app: &mut App) {
Expand Down Expand Up @@ -386,11 +395,21 @@ impl ManageStaticScripts for App {
/// Any changes to the asset settings after that will not be reflected in the asset loader.
pub trait ConfigureScriptAssetSettings {
/// Adds a supported extension to the asset settings
fn add_supported_script_extensions(&mut self, extensions: &[&'static str]) -> &mut Self;
///
/// This is only valid to call in the plugin building phase, as the asset loader will be created in the `finalize` phase.
fn add_supported_script_extensions(
&mut self,
extensions: &[&'static str],
language: Language,
) -> &mut Self;
}

impl ConfigureScriptAssetSettings for App {
fn add_supported_script_extensions(&mut self, extensions: &[&'static str]) -> &mut Self {
fn add_supported_script_extensions(
&mut self,
extensions: &[&'static str],
language: Language,
) -> &mut Self {
let mut asset_settings = self
.world_mut()
.get_resource_or_init::<ScriptAssetSettings>();
Expand All @@ -402,6 +421,11 @@ impl ConfigureScriptAssetSettings for App {
let new_arr_static = Vec::leak(new_arr);

asset_settings.supported_extensions = new_arr_static;
for extension in extensions {
asset_settings
.extension_to_language_map
.insert(*extension, language.clone());
}

self
}
Expand Down
16 changes: 3 additions & 13 deletions crates/languages/bevy_mod_scripting_lua/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
//! Lua integration for the bevy_mod_scripting system.
use bevy::{
app::Plugin,
asset::AssetPath,
ecs::{entity::Entity, world::World},
};
use bevy_mod_scripting_core::{
asset::{AssetPathToLanguageMapper, Language},
asset::Language,
bindings::{
function::namespace::Namespace, globals::AppScriptGlobalsRegistry,
script_value::ScriptValue, ThreadWorldContainer, WorldContainer,
Expand Down Expand Up @@ -60,9 +59,6 @@ impl Default for LuaScriptingPlugin {
load: lua_context_load,
reload: lua_context_reload,
},
language_mapper: AssetPathToLanguageMapper {
map: lua_language_mapper,
},
context_initializers: vec![
|_script_id, context| {
// set the world global
Expand Down Expand Up @@ -134,18 +130,12 @@ impl Default for LuaScriptingPlugin {
.map_err(ScriptError::from_mlua_error)?;
Ok(())
}],
supported_extensions: &["lua"],
additional_supported_extensions: &[],
language: Language::Lua,
},
}
}
}
#[profiling::function]
fn lua_language_mapper(path: &AssetPath) -> Language {
match path.path().extension().and_then(|ext| ext.to_str()) {
Some("lua") => Language::Lua,
_ => Language::Unknown,
}
}

impl Plugin for LuaScriptingPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
Expand Down
17 changes: 4 additions & 13 deletions crates/languages/bevy_mod_scripting_rhai/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

use bevy::{
app::Plugin,
asset::AssetPath,
ecs::{entity::Entity, world::World},
};
use bevy_mod_scripting_core::{
asset::{AssetPathToLanguageMapper, Language},
asset::Language,
bindings::{
function::namespace::Namespace, globals::AppScriptGlobalsRegistry,
script_value::ScriptValue, ThreadWorldContainer, WorldContainer,
Expand Down Expand Up @@ -84,9 +83,6 @@ impl Default for RhaiScriptingPlugin {
load: rhai_context_load,
reload: rhai_context_reload,
},
language_mapper: AssetPathToLanguageMapper {
map: rhai_language_mapper,
},
context_initializers: vec![
|_, context: &mut RhaiScriptContext| {
context.scope.set_or_push(
Expand Down Expand Up @@ -159,19 +155,14 @@ impl Default for RhaiScriptingPlugin {
context.scope.set_or_push("script_id", script.to_owned());
Ok(())
}],
supported_extensions: &["rhai"],
// already supported by BMS core
additional_supported_extensions: &[],
language: Language::Rhai,
},
}
}
}

fn rhai_language_mapper(path: &AssetPath) -> Language {
match path.path().extension().and_then(|ext| ext.to_str()) {
Some("rhai") => Language::Rhai,
_ => Language::Unknown,
}
}

impl Plugin for RhaiScriptingPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
self.scripting_plugin.build(app);
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Scripting Reference

- [Introduction](./ScriptingReference/introduction.md)
- [Constructing Arbitrary Types](./ScriptingReference/constructing-arbitrary-types.md)
- [Core Bindings](./ScriptingReference/core-api.md)
- [World](./ScriptingReference/world.md)
- [ReflectReference](./ScriptingReference/reflect-reference.md)
Expand Down
Loading
Loading