Skip to content
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ bevy_mod_scripting_lua = { workspace = true, optional = true }
bevy_mod_scripting_rhai = { workspace = true, optional = true }
bevy_mod_scripting_functions = { workspace = true }
bevy_mod_scripting_derive = { workspace = true }
bevy_mod_scripting_asset = { workspace = true }

[workspace.dependencies]
# local crates
Expand All @@ -110,6 +111,8 @@ ladfile = { path = "crates/ladfile", version = "0.5.0" }
ladfile_builder = { path = "crates/ladfile_builder", version = "0.5.1" }
bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.15.1", default-features = false }
bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.15.1", default-features = false }
bevy_mod_scripting_asset = { path = "crates/bevy_mod_scripting_asset", version = "0.15.1", default-features = false }

# bevy

bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.15.1" }
Expand Down Expand Up @@ -241,6 +244,7 @@ members = [
"crates/ladfile_builder",
"crates/bevy_system_reflection",
"crates/bindings/*",
"crates/bevy_mod_scripting_asset",
]
resolver = "2"
exclude = ["codegen", "crates/macro_tests", "xtask"]
Expand Down
2 changes: 1 addition & 1 deletion assets/tests/add_system/added_systems_run_in_parallel.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ digraph {
node_0 [label="bevy_asset::assets::Assets<bevy_asset::folder::LoadedFolder>::asset_events"];
node_1 [label="bevy_asset::assets::Assets<bevy_asset::assets::LoadedUntypedAsset>::asset_events"];
node_2 [label="bevy_asset::assets::Assets<()>::asset_events"];
node_3 [label="bevy_asset::assets::Assets<bevy_mod_scripting_core::asset::ScriptAsset>::asset_events"];
node_3 [label="bevy_asset::assets::Assets<bevy_mod_scripting_asset::script_asset::ScriptAsset>::asset_events"];
node_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"];
node_5 [label="script_integration_test_harness::dummy_before_post_update_system"];
node_6 [label="script_integration_test_harness::dummy_post_update_system"];
Expand Down
2 changes: 1 addition & 1 deletion assets/tests/add_system/added_systems_run_in_parallel.rhai
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ digraph {
node_0 [label="bevy_asset::assets::Assets<bevy_asset::folder::LoadedFolder>::asset_events"];
node_1 [label="bevy_asset::assets::Assets<bevy_asset::assets::LoadedUntypedAsset>::asset_events"];
node_2 [label="bevy_asset::assets::Assets<()>::asset_events"];
node_3 [label="bevy_asset::assets::Assets<bevy_mod_scripting_core::asset::ScriptAsset>::asset_events"];
node_3 [label="bevy_asset::assets::Assets<bevy_mod_scripting_asset::script_asset::ScriptAsset>::asset_events"];
node_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"];
node_5 [label="script_integration_test_harness::dummy_before_post_update_system"];
node_6 [label="script_integration_test_harness::dummy_post_update_system"];
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_mod_scripting_asset/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "bevy_mod_scripting_asset"
version = "0.15.1"
authors = ["Maksymilian Mozolewski <[email protected]>"]
edition = "2024"
license = "MIT OR Apache-2.0"
description = "Core traits and structures required for other parts of bevy_mod_scripting"
repository = "https://github.com/makspll/bevy_mod_scripting"
homepage = "https://github.com/makspll/bevy_mod_scripting"
categories = ["game-development"]
readme = "readme.md"


[dependencies]
bevy_reflect = { workspace = true }
bevy_asset = { workspace = true }
bevy_log = { workspace = true }
serde = { workspace = true, features = ["derive"] }
[lints]
workspace = true
3 changes: 3 additions & 0 deletions crates/bevy_mod_scripting_asset/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# bevy_mod_scripting_core

This crate is a part of the ["bevy_mod_scripting" workspace](https://github.com/makspll/bevy_mod_scripting).
51 changes: 51 additions & 0 deletions crates/bevy_mod_scripting_asset/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Error definitions for the scripting asset pipeline.

use std::fmt::Display;

use bevy_asset::AssetPath;

#[derive(Debug)]
/// An error that can occur when loading or processing a script asset.
pub struct ScriptAssetError {
pub(crate) phrase: &'static str,
pub(crate) asset_path: Option<AssetPath<'static>>,
pub(crate) inner: Box<dyn std::error::Error + Send + Sync + 'static>,
}
impl ScriptAssetError {
/// Create a new script asset error
pub fn new(
phrase: &'static str,
asset_path: Option<&AssetPath<'static>>,
inner: Box<dyn std::error::Error + Send + Sync + 'static>,
) -> Self {
Self {
phrase,
asset_path: asset_path.cloned(),
inner,
}
}
}

impl Display for ScriptAssetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(path) = &self.asset_path {
write!(
f,
"Error {}. while processing script asset '{}': {}",
self.phrase, path, self.inner
)
} else {
write!(
f,
"Error {}. while processing script asset: {}",
self.phrase, self.inner
)
}
}
}

impl std::error::Error for ScriptAssetError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.inner.as_ref())
}
}
84 changes: 84 additions & 0 deletions crates/bevy_mod_scripting_asset/src/language.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Defines supported scripting languages and their file extensions.

use serde::{Deserialize, Serialize};
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, Default, Serialize, Deserialize)]
pub enum Language {
/// The Rhai scripting language
Rhai,
/// The Lua scripting language
Lua,
/// The Rune scripting language
Rune,
/// An external scripting language
External(Cow<'static, str>),
/// Set if none of the asset path to language mappers match
#[default]
Unknown,
}

impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Language::Rhai => "Rhai".fmt(f),
Language::Lua => "Lua".fmt(f),
Language::Rune => "Rune".fmt(f),
Language::External(cow) => cow.fmt(f),
Language::Unknown => "Unknown".fmt(f),
}
}
}

/// Collect the language extensions supported during initialization.
///
/// NOTE: This resource is removed after plugin setup.
#[derive(Debug)]
pub struct LanguageExtensions(Vec<&'static str>, Vec<Language>);

impl LanguageExtensions {
/// Create a new language extensions mapping from an iterator of (extension, language) pairs.
pub fn new(iter: impl IntoIterator<Item = (&'static str, Language)>) -> Self {
let (extensions, languages): (Vec<&'static str>, Vec<Language>) = iter.into_iter().unzip();
Self(extensions, languages)
}

/// Retrieves the language for the given file extension, if it exists.
pub fn get(&self, extension: &str) -> Option<&Language> {
self.0
.iter()
.position(|&ext| ext.eq_ignore_ascii_case(extension))
.and_then(|index| self.1.get(index))
}

/// Inserts a new (extension, language) pair into the mapping.
pub fn insert(&mut self, extension: &'static str, language: Language) {
if let Some(pos) = self
.0
.iter()
.position(|&ext| ext.eq_ignore_ascii_case(extension))
{
self.1[pos] = language;
} else {
self.0.push(extension);
self.1.push(language);
}
}

/// Returns a slice of all supported file extensions.
pub fn extensions(&self) -> &[&str] {
self.0.as_slice()
}
}

impl Default for LanguageExtensions {
fn default() -> Self {
LanguageExtensions::new([
("lua", Language::Lua),
("luau", Language::Lua),
("rhai", Language::Rhai),
("rn", Language::Rune),
])
}
}
8 changes: 8 additions & 0 deletions crates/bevy_mod_scripting_asset/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! All things asset and scripting related.

pub mod error;
pub mod language;
pub mod loader;
pub mod script_asset;

pub use {error::*, language::*, loader::*, script_asset::*};
105 changes: 105 additions & 0 deletions crates/bevy_mod_scripting_asset/src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! A loader pipeline for script assets

use bevy_asset::AssetLoader;
use bevy_log::warn;
use serde::{Deserialize, Serialize};

use crate::{Language, LanguageExtensions, ScriptAsset, ScriptAssetError};

/// Script settings
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScriptSettings {
/// Define the language for a script or use the extension if None.
pub language: Option<Language>,
}

/// A loader for script assets
pub struct ScriptAssetLoader {
/// The file extensions this loader should handle
language_extensions: &'static LanguageExtensions,
/// preprocessor to run on the script before saving the content to an asset
pub preprocessor: Option<Box<dyn Fn(&mut [u8]) -> Result<(), ScriptAssetError> + Send + Sync>>,
}

impl ScriptAssetLoader {
/// Create a new script asset loader for the given extensions.
pub fn new(language_extensions: &'static LanguageExtensions) -> Self {
Self {
language_extensions,
preprocessor: None,
}
}

/// Add a preprocessor
pub fn with_preprocessor(
mut self,
preprocessor: Box<dyn Fn(&mut [u8]) -> Result<(), ScriptAssetError> + Send + Sync>,
) -> Self {
self.preprocessor = Some(preprocessor);
self
}
}

impl AssetLoader for ScriptAssetLoader {
type Asset = ScriptAsset;

type Settings = ScriptSettings;

type Error = ScriptAssetError;

async fn load(
&self,
reader: &mut dyn bevy_asset::io::Reader,
settings: &Self::Settings,
load_context: &mut bevy_asset::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut content = Vec::new();
reader.read_to_end(&mut content).await.map_err(|e| {
ScriptAssetError::new(
"reading from disk",
Some(load_context.asset_path()),
Box::new(e),
)
})?;
if let Some(processor) = &self.preprocessor {
processor(&mut content)?;
}
let language = settings.language.clone().unwrap_or_else(|| {
let ext = load_context
.path()
.extension()
.and_then(|e| e.to_str())
.unwrap_or_default();
self.language_extensions
.get(ext)
.cloned()
.unwrap_or_else(|| {
warn!("Unknown language for {:?}", load_context.path().display());
Language::Unknown
})
});
// if language == Language::Lua && cfg!(not(feature = "mlua")) {
// warn_once!(
// "Script {:?} is a Lua script but the {:?} feature is not enabled; the script will not be evaluated.",
// load_context.path().display(),
// "mlua"
// );
// }
// if language == Language::Rhai && cfg!(not(feature = "rhai")) {
// warn_once!(
// "Script {:?} is a Rhai script but the {:?} feature is not enabled; the script will not be evaluated.",
// load_context.path().display(),
// "rhai"
// );
// }
let asset = ScriptAsset {
content: content.into_boxed_slice(),
language,
};
Ok(asset)
}

fn extensions(&self) -> &[&str] {
self.language_extensions.extensions()
}
}
32 changes: 32 additions & 0 deletions crates/bevy_mod_scripting_asset/src/script_asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Scripting asset definitions

use bevy_asset::Asset;
use bevy_reflect::Reflect;

use crate::Language;

/// Represents a script loaded into memory as an asset
#[derive(Asset, Clone, Reflect)]
#[reflect(opaque)]
pub struct ScriptAsset {
/// The body of the script
pub content: Box<[u8]>, // Any chance a Cow<'static, ?> could work here?
/// The language of the script
pub language: Language,
}

impl From<String> for ScriptAsset {
fn from(s: String) -> ScriptAsset {
ScriptAsset {
content: s.into_bytes().into_boxed_slice(),
language: Language::default(),
}
}
}

impl ScriptAsset {
/// Create a new script asset with an unknown language.
pub fn new(s: impl Into<String>) -> Self {
s.into().into()
}
}
8 changes: 5 additions & 3 deletions crates/bevy_mod_scripting_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ mlua_impls = ["mlua"]
rhai_impls = ["rhai"]

[dependencies]
bevy_mod_scripting_derive = { workspace = true }
bevy_mod_scripting_asset = { workspace = true }
bevy_system_reflection = { workspace = true }


mlua = { workspace = true, optional = true }
rhai = { workspace = true, features = ["sync"], optional = true }

Expand All @@ -40,16 +45,13 @@ parking_lot = { workspace = true }
smallvec = { workspace = true }
itertools = { workspace = true }
profiling = { workspace = true }
bevy_mod_scripting_derive = { workspace = true }
fixedbitset = { workspace = true }
bevy_system_reflection = { workspace = true }
serde = { workspace = true, features = ["derive"] }
uuid = { workspace = true }
variadics_please = { workspace = true }

[dev-dependencies]
test_utils = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros"] }
pretty_assertions = { workspace = true, features = ["alloc"] }

[lints]
Expand Down
Loading
Loading