Skip to content

feat: overhaul mdbook preprocessor, prettify generated docs, support dummy globals #377

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 6 commits into from
Mar 19, 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
3 changes: 2 additions & 1 deletion crates/bevy_api_gen/templates/footer.tera
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ pub struct {{ "ScriptingPlugin" | prefix_cratename | convert_case(case="upper_ca
#[script_bindings(
remote,
name = "{{ item.ident | convert_case(case="snake") }}_functions",
bms_core_path="bevy_mod_scripting_core"
bms_core_path="bevy_mod_scripting_core",
generated
)]
impl {{item.import_path}} {
{% for function in item.functions %}
Expand Down
92 changes: 58 additions & 34 deletions crates/bevy_mod_scripting_core/src/bindings/globals/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

use std::{collections::HashMap, sync::Arc};

use bevy::{app::Plugin, ecs::reflect::AppTypeRegistry};
use bevy::{
app::Plugin,
ecs::{entity::Entity, reflect::AppTypeRegistry, world::World},
};
use bevy_mod_scripting_derive::script_globals;

use crate::{bindings::{function::from::{Union, Val}, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, WorldGuard}, docgen::into_through_type_info, error::InteropError};
use crate::{
bindings::{
function::from::{Union, Val},
ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration,
WorldGuard,
},
docgen::into_through_type_info,
error::InteropError,
};

use super::AppScriptGlobalsRegistry;

Expand All @@ -22,54 +33,67 @@ impl Plugin for CoreScriptGlobalsPlugin {

fn register_static_core_globals(world: &mut bevy::ecs::world::World) {
let global_registry = world
.get_resource_or_init::<AppScriptGlobalsRegistry>()
.clone();
let type_registry = world
.get_resource_or_init::<AppTypeRegistry>()
.clone();
let mut global_registry = global_registry.write();
let type_registry = type_registry.read();
.get_resource_or_init::<AppScriptGlobalsRegistry>()
.clone();
let type_registry = world.get_resource_or_init::<AppTypeRegistry>().clone();
let mut global_registry = global_registry.write();
let type_registry = type_registry.read();

// find all reflectable types without generics
for registration in type_registry.iter() {
if !registration.type_info().generics().is_empty() {
continue;
}
// find all reflectable types without generics
for registration in type_registry.iter() {
if !registration.type_info().generics().is_empty() {
continue;
}

if let Some(global_name) = registration.type_info().type_path_table().ident() {
let documentation = "A reference to the type, allowing you to call static methods.";
let type_info = registration.type_info();
global_registry.register_static_documented_dynamic(
registration.type_id(),
into_through_type_info(type_info),
global_name.into(),
documentation.into(),
);
}
if let Some(global_name) = registration.type_info().type_path_table().ident() {
let documentation = "A reference to the type, allowing you to call static methods.";
let type_info = registration.type_info();
global_registry.register_static_documented_dynamic(
registration.type_id(),
into_through_type_info(type_info),
global_name.into(),
documentation.into(),
);
}
}

// register basic globals
global_registry.register_dummy::<World>("world", "The current ECS world.");
global_registry
.register_dummy::<Entity>("entity", "The entity this script is attached to if any.");
global_registry.register_dummy::<String>("script_id", "the name/id of this script");
}

#[script_globals(
bms_core_path = "crate",
name = "core_globals",
)]
#[script_globals(bms_core_path = "crate", name = "core_globals")]
impl CoreGlobals {
/// A cache of types normally available through the `world.get_type_by_name` function.
///
///
/// You can use this to avoid having to store type references.
fn types(guard: WorldGuard) -> Result<HashMap<String, Union<Val<ScriptTypeRegistration>, Union<Val<ScriptComponentRegistration>, Val<ScriptResourceRegistration>>>>, InteropError> {
fn types(
guard: WorldGuard,
) -> Result<
HashMap<
String,
Union<
Val<ScriptTypeRegistration>,
Union<Val<ScriptComponentRegistration>, Val<ScriptResourceRegistration>>,
>,
>,
InteropError,
> {
let type_registry = guard.type_registry();
let type_registry = type_registry.read();
let mut type_cache = HashMap::<String, _>::default();
for registration in type_registry.iter(){
for registration in type_registry.iter() {
if let Some(ident) = registration.type_info().type_path_table().ident() {
let registration = ScriptTypeRegistration::new(Arc::new(registration.clone()));
let registration = guard.clone().get_type_registration(registration)?;
let registration = registration.map_both(Val::from, |u| u.map_both(Val::from, Val::from));
let registration =
registration.map_both(Val::from, |u| u.map_both(Val::from, Val::from));
type_cache.insert(ident.to_string(), registration);
}
}

Ok(type_cache)
}
}
}
46 changes: 43 additions & 3 deletions crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ use super::{
script_value::ScriptValue,
WorldGuard,
};
use crate::{docgen::{into_through_type_info, typed_through::ThroughTypeInfo}, error::InteropError};
use crate::{
docgen::{into_through_type_info, typed_through::ThroughTypeInfo},
error::InteropError,
};
use bevy::{ecs::system::Resource, reflect::Typed, utils::hashbrown::HashMap};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, borrow::Cow, sync::Arc};

crate::private::export_all_in_modules!{
crate::private::export_all_in_modules! {
core
}

Expand Down Expand Up @@ -48,10 +51,22 @@ pub struct ScriptGlobal {
pub type_information: ThroughTypeInfo,
}

/// A dummy global variable that documents globals set via alternative ways.
pub struct ScriptGlobalDummy {
/// The type ID of the global variable.
pub type_id: TypeId,
/// Rich type information the global variable.
pub type_information: Option<ThroughTypeInfo>,

/// The documentation for the global dummy variable.
pub documentation: Option<Cow<'static, str>>,
}

/// A registry of global variables that can be exposed to scripts.
#[derive(Default)]
pub struct ScriptGlobalsRegistry {
globals: HashMap<Cow<'static, str>, ScriptGlobal>,
dummies: HashMap<Cow<'static, str>, ScriptGlobalDummy>,
}

impl ScriptGlobalsRegistry {
Expand Down Expand Up @@ -85,6 +100,11 @@ impl ScriptGlobalsRegistry {
self.globals.iter_mut()
}

/// Iterates over the dummies in the registry
pub fn iter_dummies(&self) -> impl Iterator<Item = (&Cow<'static, str>, &ScriptGlobalDummy)> {
self.dummies.iter()
}

fn type_erase_maker<
T: ScriptReturn,
F: Fn(WorldGuard) -> Result<T, InteropError> + Send + Sync + 'static,
Expand Down Expand Up @@ -114,6 +134,26 @@ impl ScriptGlobalsRegistry {
)
}

/// Registers a dummy global into the registry.
/// Dummies are not actually exposed to languages but exist purely for the purpose of documentation.
/// This can be useful for globals which you cannot expose normally.
///
/// Dummy globals are stored as non-static instances, i.e. they're expected to be values not type references.
pub fn register_dummy<T: 'static>(
&mut self,
name: impl Into<Cow<'static, str>>,
documentation: impl Into<Cow<'static, str>>,
) {
self.dummies.insert(
name.into(),
ScriptGlobalDummy {
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: None,
},
);
}

/// Inserts a global into the registry, returns the previous value if it existed.
///
/// This is a version of [`Self::register`] which stores type information regarding the global.
Expand Down Expand Up @@ -153,7 +193,7 @@ impl ScriptGlobalsRegistry {
/// Registers a static global into the registry.
///
/// This is a version of [`Self::register_static`] which stores rich type information regarding the global.
pub fn register_static_documented<T: TypedScriptReturn +'static>(
pub fn register_static_documented<T: TypedScriptReturn + 'static>(
&mut self,
name: Cow<'static, str>,
documentation: Cow<'static, str>,
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_mod_scripting_core/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ crate::private::export_all_in_modules! {
script_system,
script_value,
world,
type_data
}
32 changes: 27 additions & 5 deletions crates/bevy_mod_scripting_core/src/bindings/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,28 @@ use bevy::{
};
use std::{any::TypeId, collections::VecDeque, sync::Arc};

/// A wrapper around a `TypeRegistration` that provides additional information about the type.
/// A reference to a type which is not a `Resource` or `Component`.
///
/// This is used as a hook to a rust type from a scripting language. We should be able to easily convert between a type name and a [`ScriptTypeRegistration`].
/// In general think of this as a handle to a type.
#[derive(Clone, Reflect)]
#[reflect(opaque)]
pub struct ScriptTypeRegistration {
pub(crate) registration: Arc<TypeRegistration>,
}

#[derive(Clone, Reflect, Debug)]
/// A registration for a component type.
/// A reference to a component type's reflection registration.
///
/// In general think of this as a handle to a type.
pub struct ScriptComponentRegistration {
pub(crate) registration: ScriptTypeRegistration,
pub(crate) component_id: ComponentId,
}

#[derive(Clone, Reflect, Debug)]
/// A registration for a resource type.
/// A reference to a resource type's reflection registration.
///
/// In general think of this as a handle to a type.
pub struct ScriptResourceRegistration {
pub(crate) registration: ScriptTypeRegistration,
pub(crate) resource_id: ComponentId,
Expand Down Expand Up @@ -134,7 +138,25 @@ impl std::fmt::Display for ScriptTypeRegistration {

#[derive(Clone, Default, Reflect)]
#[reflect(opaque)]
/// A builder for a query.
/// The query builder is used to build ECS queries which retrieve spefific components filtered by specific conditions.
///
/// For example:
/// ```rust,ignore
/// builder.component(componentA)
/// .component(componentB)
/// .with(componentC)
/// .without(componentD)
/// ```
///
/// Will retrieve entities which:
/// - Have componentA
/// - Have componentB
/// - Have componentC
/// - Do not have componentD
///
/// As well as references to components:
/// - componentA
/// - componentB
pub struct ScriptQueryBuilder {
pub(crate) components: Vec<ScriptComponentRegistration>,
with: Vec<ScriptComponentRegistration>,
Expand Down
9 changes: 7 additions & 2 deletions crates/bevy_mod_scripting_core/src/bindings/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ use bevy::{
};
use std::{any::TypeId, fmt::Debug};

/// An accessor to a `dyn PartialReflect` struct, stores a base ID of the type and a reflection path
/// safe to build but to reflect on the value inside you need to ensure aliasing rules are upheld
/// A reference to an arbitrary reflected instance.
///
/// The reference can point to either the ECS, or to the allocator.
///
/// References are composed of two parts:
/// - The base kind, which specifies where the reference points to
/// - The path, which specifies how to access the value from the base
#[derive(Debug, Clone, PartialEq, Eq, Reflect)]
#[reflect(Default, opaque)]
pub struct ReflectReference {
Expand Down
42 changes: 42 additions & 0 deletions crates/bevy_mod_scripting_core/src/bindings/type_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Contains various `Reflect` type data we use in BMS.

use bevy::reflect::FromType;

/// A marker type to indicate that a type is generated.
///
/// To mark a type as generated use:
/// ```rust,ignore
/// registry.register_type_data::<T, MarkAsGenerated>();
/// ```
#[derive(Clone, Copy)]
pub struct MarkAsGenerated;

impl<T> FromType<T> for MarkAsGenerated {
fn from_type() -> Self {
Self
}
}

/// A marker type to indicate that a type is significant.
///
/// Significant types appear "before" core types in documentation
#[derive(Clone, Copy)]
pub struct MarkAsSignificant;

impl<T> FromType<T> for MarkAsSignificant {
fn from_type() -> Self {
Self
}
}

/// A marker type to indicate that a type is core to BMS.
///
/// core types appear before insignificant types in documentation.
#[derive(Clone, Copy)]
pub struct MarkAsCore;

impl<T> FromType<T> for MarkAsCore {
fn from_type() -> Self {
Self
}
}
Loading
Loading