Skip to content

Commit 72cb3ba

Browse files
authored
feat(bms,ladfile_builder): introduce app global instance registry and export them in ladfile_builder (#340)
feat: introduce app global instance registry and export them in `ladfile_builder`
1 parent de753db commit 72cb3ba

File tree

16 files changed

+896
-278
lines changed

16 files changed

+896
-278
lines changed

.github/workflows/bevy_mod_scripting.yml

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,20 @@ jobs:
8585
matrix:
8686
run_args: ${{fromJson(needs.generate-job-matrix.outputs.matrix)}}
8787
steps:
88-
- name: Free Disk Space (Ubuntu)
89-
if: runner.os == 'Linux'
90-
uses: jlumbroso/free-disk-space@main
91-
with:
92-
tool-cache: false
93-
android: true
94-
dotnet: true
95-
haskell: true
96-
large-packages: true
97-
docker-images: true
98-
swap-storage: true
99-
# - if: runner.os == 'linux'
100-
# run: |
101-
# sudo rm -rf /usr/share/dotnet; sudo rm -rf /opt/ghc; sudo rm -rf "/usr/local/share/boost"; sudo rm -rf "$AGENT_TOOLSDIRECTORY"
88+
# - name: Free Disk Space (Ubuntu)
89+
# if: runner.os == 'Linux'
90+
# uses: jlumbroso/free-disk-space@main
91+
# with:
92+
# tool-cache: false
93+
# android: true
94+
# dotnet: true
95+
# haskell: true
96+
# large-packages: true
97+
# docker-images: true
98+
# swap-storage: true
99+
# # - if: runner.os == 'linux'
100+
# # run: |
101+
# # sudo rm -rf /usr/share/dotnet; sudo rm -rf /opt/ghc; sudo rm -rf "/usr/local/share/boost"; sudo rm -rf "$AGENT_TOOLSDIRECTORY"
102102
- name: Checkout
103103
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' }}
104104
uses: actions/checkout@v4
@@ -120,10 +120,19 @@ jobs:
120120
run: |
121121
cargo xtask init
122122
123+
- name: Setup GPU Drivers
124+
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' && matrix.run_args.requires_gpu }}
125+
run: |
126+
sudo add-apt-repository ppa:kisak/turtle -y
127+
sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
123128
- name: Check
124-
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' }}
129+
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' && !matrix.run_args.requires_gpu }}
125130
run: |
126131
${{ matrix.run_args.command }}
132+
- name: Check With virtual X11 server
133+
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' && matrix.run_args.requires_gpu }}
134+
run: |
135+
xvfb-run ${{ matrix.run_args.command }}
127136
128137
- name: Upload coverage artifact
129138
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' && matrix.run_args.generates_coverage }}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Core globals exposed by the BMS framework
2+
3+
use bevy::{app::Plugin, ecs::reflect::AppTypeRegistry};
4+
5+
use super::AppScriptGlobalsRegistry;
6+
7+
/// A plugin introducing core globals for the BMS framework
8+
pub struct CoreScriptGlobalsPlugin;
9+
10+
impl Plugin for CoreScriptGlobalsPlugin {
11+
fn build(&self, app: &mut bevy::app::App) {
12+
let global_registry = app
13+
.world_mut()
14+
.get_resource_or_init::<AppScriptGlobalsRegistry>()
15+
.clone();
16+
let type_registry = app
17+
.world_mut()
18+
.get_resource_or_init::<AppTypeRegistry>()
19+
.clone();
20+
let mut global_registry = global_registry.write();
21+
let type_registry = type_registry.read();
22+
23+
// find all reflectable types without generics
24+
for registration in type_registry.iter() {
25+
if !registration.type_info().generics().is_empty() {
26+
continue;
27+
}
28+
29+
if let Some(global_name) = registration.type_info().type_path_table().ident() {
30+
let documentation = "A reference to the type, allowing you to call static methods.";
31+
global_registry.register_static_documented_dynamic(
32+
registration.type_id(),
33+
None,
34+
global_name.into(),
35+
documentation.into(),
36+
)
37+
}
38+
}
39+
}
40+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//! Contains abstractions for exposing "globals" to scripts, in a language-agnostic way.
2+
3+
use super::{
4+
function::arg_meta::{ScriptReturn, TypedScriptReturn},
5+
script_value::ScriptValue,
6+
WorldGuard,
7+
};
8+
use crate::{docgen::typed_through::ThroughTypeInfo, error::InteropError};
9+
use bevy::{ecs::system::Resource, utils::hashbrown::HashMap};
10+
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
11+
use std::{any::TypeId, borrow::Cow, sync::Arc};
12+
13+
pub mod core;
14+
15+
/// A send + sync wrapper around the [`ScriptGlobalsRegistry`].
16+
#[derive(Default, Resource, Clone)]
17+
pub struct AppScriptGlobalsRegistry(Arc<RwLock<ScriptGlobalsRegistry>>);
18+
19+
impl AppScriptGlobalsRegistry {
20+
/// Returns a reference to the inner [`ScriptGlobalsRegistry`].
21+
pub fn read(&self) -> RwLockReadGuard<ScriptGlobalsRegistry> {
22+
self.0.read()
23+
}
24+
25+
/// Returns a mutable reference to the inner [`ScriptGlobalsRegistry`].
26+
pub fn write(&self) -> RwLockWriteGuard<ScriptGlobalsRegistry> {
27+
self.0.write()
28+
}
29+
}
30+
31+
/// A function that creates a global variable.
32+
pub type ScriptGlobalMakerFn<T> =
33+
dyn Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync;
34+
35+
/// A global variable that can be exposed to scripts.
36+
pub struct ScriptGlobal {
37+
/// The function that creates the global variable.
38+
/// if not present, this is assumed to be a static global, one that
39+
/// cannot be instantiated, but carries type information.
40+
pub maker: Option<Arc<ScriptGlobalMakerFn<ScriptValue>>>,
41+
/// The documentation for the global variable.
42+
pub documentation: Option<Cow<'static, str>>,
43+
/// The type ID of the global variable.
44+
pub type_id: TypeId,
45+
/// Rich type information the global variable.
46+
pub type_information: Option<ThroughTypeInfo>,
47+
}
48+
49+
/// A registry of global variables that can be exposed to scripts.
50+
#[derive(Default)]
51+
pub struct ScriptGlobalsRegistry {
52+
globals: HashMap<Cow<'static, str>, ScriptGlobal>,
53+
}
54+
55+
impl ScriptGlobalsRegistry {
56+
/// Gets the global with the given name
57+
pub fn get(&self, name: &str) -> Option<&ScriptGlobal> {
58+
self.globals.get(name)
59+
}
60+
61+
/// Gets the global with the given name mutably
62+
pub fn get_mut(&mut self, name: &str) -> Option<&mut ScriptGlobal> {
63+
self.globals.get_mut(name)
64+
}
65+
66+
/// Counts the number of globals in the registry
67+
pub fn len(&self) -> usize {
68+
self.globals.len()
69+
}
70+
71+
/// Checks if the registry is empty
72+
pub fn is_empty(&self) -> bool {
73+
self.len() == 0
74+
}
75+
76+
/// Iterates over the globals in the registry
77+
pub fn iter(&self) -> impl Iterator<Item = (&Cow<'static, str>, &ScriptGlobal)> {
78+
self.globals.iter()
79+
}
80+
81+
/// Iterates over the globals in the registry mutably
82+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Cow<'static, str>, &mut ScriptGlobal)> {
83+
self.globals.iter_mut()
84+
}
85+
86+
fn type_erase_maker<
87+
T: ScriptReturn,
88+
F: Fn(WorldGuard) -> Result<T, InteropError> + Send + Sync + 'static,
89+
>(
90+
maker: F,
91+
) -> Arc<ScriptGlobalMakerFn<ScriptValue>> {
92+
Arc::new(move |world| T::into_script(maker(world.clone())?, world))
93+
}
94+
95+
/// Inserts a global into the registry, returns the previous value if it existed
96+
pub fn register<
97+
T: ScriptReturn + 'static,
98+
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
99+
>(
100+
&mut self,
101+
name: Cow<'static, str>,
102+
maker: F,
103+
) -> Option<ScriptGlobal> {
104+
self.globals.insert(
105+
name,
106+
ScriptGlobal {
107+
maker: Some(Self::type_erase_maker(maker)),
108+
documentation: None,
109+
type_id: TypeId::of::<T>(),
110+
type_information: None,
111+
},
112+
)
113+
}
114+
115+
/// Inserts a global into the registry, returns the previous value if it existed.
116+
///
117+
/// This is a version of [`Self::register`] which stores type information regarding the global.
118+
pub fn register_documented<
119+
T: TypedScriptReturn + 'static,
120+
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
121+
>(
122+
&mut self,
123+
name: Cow<'static, str>,
124+
maker: F,
125+
documentation: Cow<'static, str>,
126+
) -> Option<ScriptGlobal> {
127+
self.globals.insert(
128+
name,
129+
ScriptGlobal {
130+
maker: Some(Self::type_erase_maker(maker)),
131+
documentation: Some(documentation),
132+
type_id: TypeId::of::<T>(),
133+
type_information: Some(T::through_type_info()),
134+
},
135+
)
136+
}
137+
138+
/// Registers a static global into the registry.
139+
pub fn register_static<T: 'static>(&mut self, name: Cow<'static, str>) {
140+
self.globals.insert(
141+
name,
142+
ScriptGlobal {
143+
maker: None,
144+
documentation: None,
145+
type_id: TypeId::of::<T>(),
146+
type_information: None,
147+
},
148+
);
149+
}
150+
151+
/// Registers a static global into the registry.
152+
///
153+
/// This is a version of [`Self::register_static`] which stores rich type information regarding the global.
154+
pub fn register_static_documented<T: TypedScriptReturn + 'static>(
155+
&mut self,
156+
name: Cow<'static, str>,
157+
documentation: Cow<'static, str>,
158+
) {
159+
self.globals.insert(
160+
name,
161+
ScriptGlobal {
162+
maker: None,
163+
documentation: Some(documentation),
164+
type_id: TypeId::of::<T>(),
165+
type_information: Some(T::through_type_info()),
166+
},
167+
);
168+
}
169+
170+
/// Registers a static global into the registry.
171+
///
172+
/// This is a version of [`Self::register_static_documented`] which does not require compile time type knowledge.
173+
pub fn register_static_documented_dynamic(
174+
&mut self,
175+
type_id: TypeId,
176+
type_information: Option<ThroughTypeInfo>,
177+
name: Cow<'static, str>,
178+
documentation: Cow<'static, str>,
179+
) {
180+
self.globals.insert(
181+
name,
182+
ScriptGlobal {
183+
maker: None,
184+
documentation: Some(documentation),
185+
type_id,
186+
type_information,
187+
},
188+
);
189+
}
190+
}
191+
192+
#[cfg(test)]
193+
mod test {
194+
use bevy::ecs::world::World;
195+
196+
use super::*;
197+
198+
#[test]
199+
fn test_script_globals_registry() {
200+
let mut registry = ScriptGlobalsRegistry::default();
201+
202+
let maker = |_: WorldGuard| Ok(ScriptValue::from(42));
203+
let maker2 = |_: WorldGuard| Ok(ScriptValue::from(43));
204+
205+
assert_eq!(registry.len(), 0);
206+
assert!(registry.is_empty());
207+
208+
assert!(registry.register(Cow::Borrowed("foo"), maker).is_none());
209+
assert_eq!(registry.len(), 1);
210+
211+
assert_eq!(
212+
(registry.get("foo").unwrap().maker.clone().unwrap())(WorldGuard::new(
213+
&mut World::new()
214+
))
215+
.unwrap(),
216+
ScriptValue::from(42)
217+
);
218+
219+
assert!(registry.register(Cow::Borrowed("foo"), maker2).is_some());
220+
assert_eq!(registry.len(), 1);
221+
222+
assert_eq!(
223+
(registry.get("foo").unwrap().maker.clone().unwrap())(WorldGuard::new(
224+
&mut World::new()
225+
))
226+
.unwrap(),
227+
ScriptValue::from(43)
228+
);
229+
}
230+
231+
#[test]
232+
fn test_documentation_is_stored() {
233+
let mut registry = ScriptGlobalsRegistry::default();
234+
235+
let maker = |_: WorldGuard| Ok(ScriptValue::from(42));
236+
237+
assert!(registry
238+
.register_documented(Cow::Borrowed("foo"), maker, Cow::Borrowed("This is a test"))
239+
.is_none());
240+
241+
let global = registry.get("foo").unwrap();
242+
assert_eq!(global.documentation.as_deref(), Some("This is a test"));
243+
}
244+
245+
#[test]
246+
fn test_static_globals() {
247+
let mut registry = ScriptGlobalsRegistry::default();
248+
249+
registry.register_static::<i32>(Cow::Borrowed("foo"));
250+
251+
let global = registry.get("foo").unwrap();
252+
assert!(global.maker.is_none());
253+
assert_eq!(global.type_id, TypeId::of::<i32>());
254+
255+
// the same but documented
256+
registry.register_static_documented::<i32>(
257+
Cow::Borrowed("bar"),
258+
Cow::Borrowed("This is a test"),
259+
);
260+
261+
let global = registry.get("bar").unwrap();
262+
assert!(global.maker.is_none());
263+
assert_eq!(global.type_id, TypeId::of::<i32>());
264+
assert_eq!(global.documentation.as_deref(), Some("This is a test"));
265+
}
266+
}

crates/bevy_mod_scripting_core/src/bindings/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod access_map;
44
pub mod allocator;
55
pub mod function;
6+
pub mod globals;
67
pub mod pretty_print;
78
pub mod query;
89
pub mod reference;

0 commit comments

Comments
 (0)