Skip to content

Commit 9e0cf56

Browse files
authored
feat: warn on duplicate globals (#486)
1 parent ea6bcae commit 9e0cf56

File tree

5 files changed

+84
-72
lines changed

5 files changed

+84
-72
lines changed

benches/benchmarks.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -266,22 +266,13 @@ fn conversion_benchmarks(criterion: &mut Criterion) {
266266

267267
fn script_load_benchmarks(criterion: &mut Criterion) {
268268
let mut group = criterion.benchmark_group("loading");
269-
let reload_probability = 0.5;
270269
// lua
271-
let plugin = make_test_lua_plugin();
272270
let content = include_str!("../assets/macro_benchmarks/loading/empty.lua");
273-
run_plugin_script_load_benchmark(plugin, "empty Lua", content, &mut group, reload_probability);
271+
run_plugin_script_load_benchmark(make_test_lua_plugin, "empty Lua", content, &mut group);
274272

275273
// rhai
276-
let plugin = make_test_rhai_plugin();
277274
let content = include_str!("../assets/macro_benchmarks/loading/empty.rhai");
278-
run_plugin_script_load_benchmark(
279-
plugin,
280-
"empty Rhai",
281-
content,
282-
&mut group,
283-
reload_probability,
284-
);
275+
run_plugin_script_load_benchmark(make_test_rhai_plugin, "empty Rhai", content, &mut group);
285276
}
286277

287278
pub fn benches() {

crates/bevy_mod_scripting_bindings/src/globals/core.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use ::{
77
bevy_reflect::TypeRegistration,
88
};
99
use bevy_app::App;
10+
use bevy_log::warn;
1011
use bevy_mod_scripting_asset::ScriptAsset;
1112
use bevy_mod_scripting_derive::script_globals;
1213
use bevy_platform::collections::HashMap;
@@ -63,6 +64,7 @@ impl Plugin for CoreScriptGlobalsPlugin {
6364
register_core_globals(app.world_mut());
6465
}
6566
}
67+
const MSG_DUPLICATE_GLOBAL: &str = "This can cause confusing issues for script users, use `CoreScriptGlobalsPlugin.filter` to filter out uneeded duplicate types.";
6668

6769
#[profiling::function]
6870
fn register_static_core_globals(world: &mut World, filter: fn(&TypeRegistration) -> bool) {
@@ -82,20 +84,41 @@ fn register_static_core_globals(world: &mut World, filter: fn(&TypeRegistration)
8284
if let Some(global_name) = registration.type_info().type_path_table().ident() {
8385
let documentation = "A reference to the type, allowing you to call static methods.";
8486
let type_info = registration.type_info();
85-
global_registry.register_static_documented_dynamic(
86-
registration.type_id(),
87-
into_through_type_info(type_info),
88-
global_name.into(),
89-
documentation.into(),
90-
);
87+
if global_registry
88+
.register_static_documented_dynamic(
89+
registration.type_id(),
90+
into_through_type_info(type_info),
91+
global_name.into(),
92+
documentation.into(),
93+
)
94+
.is_some()
95+
{
96+
warn!(
97+
"Duplicate global registration for type: '{}'. {MSG_DUPLICATE_GLOBAL}",
98+
type_info.type_path_table().short_path()
99+
)
100+
};
91101
}
92102
}
93103

94104
// register basic globals
95-
global_registry.register_dummy::<World>("world", "The current ECS world.");
96-
global_registry
97-
.register_dummy::<Entity>("entity", "The entity this script is attached to if any.");
98-
global_registry.register_dummy_typed::<Val<Handle<ScriptAsset>>>("script_asset", "the asset handle for this script. If the asset is ever unloaded, the handle will be less useful.");
105+
if global_registry
106+
.register_dummy::<World>("world", "The current ECS world.")
107+
.is_some()
108+
{
109+
warn!("existing `world` global was replaced by the core `world` dummy type.")
110+
};
111+
112+
if global_registry
113+
.register_dummy::<Entity>("entity", "The entity this script is attached to if any.")
114+
.is_some()
115+
{
116+
warn!("existing `entity` global was replaced by the core `entity` dummy type.")
117+
}
118+
119+
if global_registry.register_dummy_typed::<Val<Handle<ScriptAsset>>>("script_asset", "the asset handle for this script. If the asset is ever unloaded, the handle will be less useful.").is_some() {
120+
warn!("existing `script_asset` global was replaced by the core `script_asset` dummy type.")
121+
};
99122
}
100123

101124
#[script_globals(bms_bindings_path = "crate", name = "core_globals")]
@@ -128,7 +151,15 @@ impl CoreGlobals {
128151
let registration = guard.clone().get_type_registration(registration)?;
129152
let registration =
130153
registration.map_both(Val::from, |u| u.map_both(Val::from, Val::from));
131-
type_cache.insert(type_path.to_owned(), registration);
154+
if type_cache
155+
.insert(type_path.to_owned(), registration)
156+
.is_some()
157+
{
158+
warn!(
159+
"duplicate entry inside `types` global for type: {}. {MSG_DUPLICATE_GLOBAL}",
160+
type_path
161+
)
162+
};
132163
}
133164

134165
Ok(type_cache)

crates/bevy_mod_scripting_bindings/src/globals/mod.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ impl ScriptGlobalsRegistry {
116116
}
117117

118118
/// Inserts a global into the registry, returns the previous value if it existed
119+
#[must_use]
119120
pub fn register<
120121
T: ScriptReturn + 'static + Typed,
121122
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
@@ -140,40 +141,43 @@ impl ScriptGlobalsRegistry {
140141
/// This can be useful for globals which you cannot expose normally.
141142
///
142143
/// Dummy globals are stored as non-static instances, i.e. they're expected to be values not type references.
144+
#[must_use]
143145
pub fn register_dummy<T: 'static>(
144146
&mut self,
145147
name: impl Into<Cow<'static, str>>,
146148
documentation: impl Into<Cow<'static, str>>,
147-
) {
149+
) -> Option<ScriptGlobalDummy> {
148150
self.dummies.insert(
149151
name.into(),
150152
ScriptGlobalDummy {
151153
documentation: Some(documentation.into()),
152154
type_id: TypeId::of::<T>(),
153155
type_information: None,
154156
},
155-
);
157+
)
156158
}
157159

158160
/// Typed equivalent to [`Self::register_dummy`].
161+
#[must_use]
159162
pub fn register_dummy_typed<T: 'static + TypedThrough>(
160163
&mut self,
161164
name: impl Into<Cow<'static, str>>,
162165
documentation: impl Into<Cow<'static, str>>,
163-
) {
166+
) -> Option<ScriptGlobalDummy> {
164167
self.dummies.insert(
165168
name.into(),
166169
ScriptGlobalDummy {
167170
documentation: Some(documentation.into()),
168171
type_id: TypeId::of::<T>(),
169172
type_information: Some(T::through_type_info()),
170173
},
171-
);
174+
)
172175
}
173176

174177
/// Inserts a global into the registry, returns the previous value if it existed.
175178
///
176179
/// This is a version of [`Self::register`] which stores type information regarding the global.
180+
#[must_use]
177181
pub fn register_documented<
178182
T: TypedScriptReturn + 'static,
179183
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
@@ -195,7 +199,11 @@ impl ScriptGlobalsRegistry {
195199
}
196200

197201
/// Registers a static global into the registry.
198-
pub fn register_static<T: 'static + Typed>(&mut self, name: Cow<'static, str>) {
202+
#[must_use]
203+
pub fn register_static<T: 'static + Typed>(
204+
&mut self,
205+
name: Cow<'static, str>,
206+
) -> Option<ScriptGlobal> {
199207
self.globals.insert(
200208
name,
201209
ScriptGlobal {
@@ -204,17 +212,18 @@ impl ScriptGlobalsRegistry {
204212
type_id: TypeId::of::<T>(),
205213
type_information: into_through_type_info(T::type_info()),
206214
},
207-
);
215+
)
208216
}
209217

210218
/// Registers a static global into the registry.
211219
///
212220
/// This is a version of [`Self::register_static`] which stores rich type information regarding the global.
221+
#[must_use]
213222
pub fn register_static_documented<T: TypedScriptReturn + 'static>(
214223
&mut self,
215224
name: Cow<'static, str>,
216225
documentation: Cow<'static, str>,
217-
) {
226+
) -> Option<ScriptGlobal> {
218227
self.globals.insert(
219228
name,
220229
ScriptGlobal {
@@ -223,19 +232,20 @@ impl ScriptGlobalsRegistry {
223232
type_id: TypeId::of::<T>(),
224233
type_information: T::through_type_info(),
225234
},
226-
);
235+
)
227236
}
228237

229238
/// Registers a static global into the registry.
230239
///
231240
/// This is a version of [`Self::register_static_documented`] which does not require compile time type knowledge.
241+
#[must_use]
232242
pub fn register_static_documented_dynamic(
233243
&mut self,
234244
type_id: TypeId,
235245
type_information: ThroughTypeInfo,
236246
name: Cow<'static, str>,
237247
documentation: Cow<'static, str>,
238-
) {
248+
) -> Option<ScriptGlobal> {
239249
self.globals.insert(
240250
name,
241251
ScriptGlobal {
@@ -244,7 +254,7 @@ impl ScriptGlobalsRegistry {
244254
type_id,
245255
type_information,
246256
},
247-
);
257+
)
248258
}
249259
}
250260

@@ -307,14 +317,14 @@ mod test {
307317
fn test_static_globals() {
308318
let mut registry = ScriptGlobalsRegistry::default();
309319

310-
registry.register_static::<i32>(Cow::Borrowed("foo"));
320+
_ = registry.register_static::<i32>(Cow::Borrowed("foo"));
311321

312322
let global = registry.get("foo").unwrap();
313323
assert!(global.maker.is_none());
314324
assert_eq!(global.type_id, TypeId::of::<i32>());
315325

316326
// the same but documented
317-
registry.register_static_documented::<i32>(
327+
_ = registry.register_static_documented::<i32>(
318328
Cow::Borrowed("bar"),
319329
Cow::Borrowed("This is a test"),
320330
);

crates/bevy_mod_scripting_derive/src/derive/script_globals.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ pub fn script_globals(
4242
let registry = world.get_resource_or_init::<#bms_bindings_path::globals::AppScriptGlobalsRegistry>();
4343
let mut registry = registry.write();
4444

45-
registry
46-
#(#function_registrations)*;
45+
#(
46+
if (registry #function_registrations).is_some() {
47+
warn!("conflicting global registration under name: {}. This might cause confusing problems, use `CoreScriptGlobalsPlugin.filter` to filter out uneeded duplicate types.", stringify!(#function_name))
48+
}
49+
)*;
4750
}
4851
};
4952

crates/testing_crates/script_integration_test_harness/src/lib.rs

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ pub mod test_functions;
44

55
use std::{
66
path::PathBuf,
7-
sync::Mutex,
87
time::{Duration, Instant},
98
};
109

@@ -22,7 +21,6 @@ use ::{
2221
bevy_reflect::Reflect,
2322
};
2423
use bevy_asset::Assets;
25-
use bevy_ecs::world::World;
2624
use bevy_mod_scripting_asset::ScriptAsset;
2725
use bevy_mod_scripting_bindings::{
2826
CoreScriptGlobalsPlugin, ReflectAccessId, ThreadWorldContainer, WorldAccessGuard,
@@ -340,61 +338,40 @@ where
340338

341339
pub fn run_plugin_script_load_benchmark<
342340
P: IntoScriptPluginParams + Plugin + FromWorld,
341+
F: Fn() -> P,
343342
M: Measurement,
344343
>(
345-
plugin: P,
344+
plugin_maker: F,
346345
benchmark_id: &str,
347346
content: &str,
348347
criterion: &mut criterion::BenchmarkGroup<M>,
349-
reload_probability: f32,
350348
) {
351-
let mut app = setup_integration_test(|_, _| {});
352-
install_test_plugin(&mut app, false);
353-
app.add_plugins(plugin);
354349
let content_boxed = content.to_string().into_bytes().into_boxed_slice();
355-
let mut rng_guard = RNG.lock().unwrap();
356-
*rng_guard = rand_chacha::ChaCha12Rng::from_seed([42u8; 32]);
357-
drop(rng_guard);
358-
359-
let world_ptr = app.world_mut() as *mut World;
360-
let world_guard = Mutex::<()>::new(());
361350

362351
criterion.bench_function(benchmark_id, |c| {
363352
c.iter_batched(
364353
|| {
365-
let mut rng = RNG.lock().unwrap();
366-
let is_reload = rng.random_range(0f32..=1f32) < reload_probability;
354+
let mut app = setup_integration_test(|_, _| {});
355+
install_test_plugin(&mut app, false);
356+
app.add_plugins(plugin_maker());
367357

368-
let guard = world_guard.lock().unwrap();
369358
// Safety: we claimed a unique guard, only code accessing this will need to do the same
370-
let world = unsafe { &mut *world_ptr };
359+
let world = app.world_mut();
371360
let mut assets = world.get_resource_or_init::<Assets<ScriptAsset>>();
372361

373-
let id = is_reload
374-
.then(|| assets.ids().next())
375-
.flatten()
376-
.and_then(|id| assets.get_strong_handle(id))
377-
.unwrap_or_else(|| {
378-
assets.add(ScriptAsset {
379-
content: content_boxed.clone(),
380-
language: P::LANGUAGE,
381-
})
382-
});
383-
drop(guard);
362+
let id = assets.add(ScriptAsset {
363+
content: content_boxed.clone(),
364+
language: P::LANGUAGE,
365+
});
384366

385367
// We manually load the script inside a command.
386368
(
369+
app,
387370
AttachScript::<P>::new(ScriptAttachment::StaticScript(id)),
388-
is_reload,
389371
)
390372
},
391-
|(command, _is_reload)| {
392-
tracing::event!(
393-
Level::TRACE,
394-
"profiling_iter {} is reload?: {}",
395-
benchmark_id,
396-
_is_reload
397-
);
373+
|(mut app, command)| {
374+
tracing::event!(Level::TRACE, "profiling_iter {}", benchmark_id);
398375
command.apply(app.world_mut());
399376
},
400377
BatchSize::LargeInput,

0 commit comments

Comments
 (0)