Skip to content

Commit fe90eaf

Browse files
committed
actually release access after function calls
1 parent 4034c1c commit fe90eaf

File tree

8 files changed

+132
-32
lines changed

8 files changed

+132
-32
lines changed

crates/bevy_mod_scripting_core/src/bindings/access_map.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,14 @@ impl AccessMap {
279279
.collect()
280280
}
281281

282-
pub fn count_thread_acceesses(&self) -> usize {
283-
self.individual_accesses
284-
.iter()
285-
.filter(|e| {
286-
e.value()
287-
.read_by
288-
.iter()
289-
.any(|o| o.id == std::thread::current().id())
290-
})
291-
.count()
282+
pub fn count_accesses(&self) -> usize {
283+
self.individual_accesses.len()
284+
}
285+
286+
pub fn release_all_accesses(&self) {
287+
self.individual_accesses.clear();
288+
self.global_lock
289+
.store(false, std::sync::atomic::Ordering::Relaxed);
292290
}
293291

294292
pub fn access_location<K: AccessMapKey>(

crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,25 @@ macro_rules! impl_script_function {
144144
let res: Result<ScriptValue, InteropError> = (|| {
145145
$( let $callback = world.clone(); )?
146146
let world = world.try_read()?;
147-
// TODO: snapshot the accesses and release them after
148-
#[allow(unused_mut,unused_variables)]
149-
let mut current_arg = 0;
150-
$(
151-
current_arg += 1;
152-
let $param = <$param>::from_script($param, world.clone())
153-
.map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?;
154-
)*
155-
let out = self( $( $callback, )? $( $param.into(), )* );
156-
$(
157-
let $out = out?;
158-
let out = $out;
159-
)?
160-
out.into_script(world.clone()).map_err(|e| InteropError::function_arg_conversion_error("return value".to_owned(), e))
147+
world.begin_access_scope()?;
148+
let ret = {
149+
#[allow(unused_mut,unused_variables)]
150+
let mut current_arg = 0;
151+
$(
152+
current_arg += 1;
153+
let $param = <$param>::from_script($param, world.clone())
154+
.map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?;
155+
)*
156+
let out = self( $( $callback, )? $( $param.into(), )* );
157+
$(
158+
let $out = out?;
159+
let out = $out;
160+
)?
161+
out.into_script(world.clone()).map_err(|e| InteropError::function_arg_conversion_error("return value".to_owned(), e))
162+
};
163+
// Safety: we're not holding any references to the world, the arguments which might have aliased have been dropped
164+
unsafe { world.end_access_scope()? };
165+
ret
161166
})();
162167
let script_value: ScriptValue = res.into();
163168
script_value

crates/bevy_mod_scripting_core/src/bindings/world.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,22 @@ impl<'w> WorldAccessGuard<'w> {
289289
}))
290290
}
291291

292+
/// Begins a new access scope. Currently this simply throws an erorr if there are any accesses held. Should only be used in a single-threaded context
293+
pub(crate) fn begin_access_scope(&self) -> Result<(), InteropError> {
294+
if self.0.accesses.count_accesses() != 0 {
295+
return Err(InteropError::invalid_access_count(self.0.accesses.count_accesses(), 0, "When beginning access scope, presumably for a function call, some accesses are still held".to_owned()));
296+
}
297+
298+
Ok(())
299+
}
300+
301+
/// Ends the access scope, releasing all accesses. Should only be used in a single-threaded context
302+
pub(crate) unsafe fn end_access_scope(&self) -> Result<(), InteropError> {
303+
self.0.accesses.release_all_accesses();
304+
305+
Ok(())
306+
}
307+
292308
/// Purely debugging utility to list all accesses currently held.
293309
pub fn list_accesses(&self) -> Vec<(ReflectAccessId, AccessCount)> {
294310
self.0.accesses.list_accesses()

crates/bevy_mod_scripting_core/src/error.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,14 @@ impl InteropError {
392392
}))
393393
}
394394

395+
pub fn invalid_access_count(count: usize, expected: usize, context: String) -> Self {
396+
Self(Arc::new(InteropErrorInner::InvalidAccessCount {
397+
count,
398+
expected,
399+
context,
400+
}))
401+
}
402+
395403
pub fn inner(&self) -> &InteropErrorInner {
396404
&self.0
397405
}
@@ -429,6 +437,11 @@ pub enum InteropErrorInner {
429437
base: ReflectBaseType,
430438
location: Option<std::panic::Location<'static>>,
431439
},
440+
InvalidAccessCount {
441+
count: usize,
442+
expected: usize,
443+
context: String,
444+
},
432445
ImpossibleConversion {
433446
into: TypeId,
434447
},
@@ -645,6 +658,9 @@ impl DisplayWithWorld for InteropErrorInner {
645658
InteropErrorInner::LengthMismatch { expected, got } => {
646659
format!("Array/List Length mismatch, expected: {}, got: {}", expected, got)
647660
},
661+
InteropErrorInner::InvalidAccessCount { count, expected, context } => {
662+
format!("Invalid access count, expected: {}, got: {}. {}", expected, count, context)
663+
},
648664
}
649665
}
650666
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local entity = Entity.from_raw(9999);
2+
_claim_global_access();
3+
4+
if pcall(function()
5+
entity:eq(entity)
6+
end)
7+
then
8+
error("Aliasing access did not panick")
9+
else
10+
-- all good
11+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local entity = Entity.from_raw(9999);
2+
_claim_write_access(entity);
3+
4+
if pcall(function()
5+
entity:eq(entity)
6+
end)
7+
then
8+
error("Aliasing access did not panick")
9+
else
10+
-- all good
11+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
local entity = Entity.from_raw(9999);
2+
-- does not throw
3+
entity:eq(entity);

crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use bevy::{
77
};
88
use bevy_mod_scripting_core::{
99
bindings::{
10-
pretty_print::DisplayWithWorld, ReflectAllocator, ReflectReference, ScriptTypeRegistration,
11-
WorldAccessGuard, WorldCallbackAccess,
10+
access_map::ReflectAccessId, pretty_print::DisplayWithWorld, ReflectAllocator,
11+
ReflectReference, ScriptTypeRegistration, WorldAccessGuard, WorldCallbackAccess,
1212
},
1313
context::ContextLoadingSettings,
1414
error::ScriptError,
@@ -97,6 +97,16 @@ fn init_lua_test_utils(_script_name: &Cow<'static, str>, lua: &mut Lua) -> Resul
9797
let assert_throws = lua
9898
.create_function(|lua, (f, regex): (LuaFunction, String)| {
9999
let world = lua.get_world();
100+
101+
// let result = match std::panic::catch_unwind(|| ) {
102+
// Ok(e) => e,
103+
// Err(panic) => {
104+
// return Err(mlua::Error::RuntimeError(format!(
105+
// "Function panicked: {:?}",
106+
// panic
107+
// )))
108+
// }
109+
// };
100110
let result = f.call::<()>(());
101111
let err = match result {
102112
Ok(_) => {
@@ -120,6 +130,34 @@ fn init_lua_test_utils(_script_name: &Cow<'static, str>, lua: &mut Lua) -> Resul
120130
})
121131
.unwrap();
122132

133+
let set_write_access = lua
134+
.create_function(|lua, val: LuaReflectReference| {
135+
let world = lua.get_world();
136+
let inner: ReflectReference = val.into();
137+
138+
world.claim_write_access(ReflectAccessId::for_reference(inner.base.base_id).unwrap());
139+
Ok(())
140+
})
141+
.unwrap();
142+
143+
let set_read_access = lua
144+
.create_function(|lua, val: LuaReflectReference| {
145+
let world = lua.get_world();
146+
let inner: ReflectReference = val.into();
147+
148+
world.claim_read_access(ReflectAccessId::for_reference(inner.base.base_id).unwrap());
149+
Ok(())
150+
})
151+
.unwrap();
152+
153+
let claim_whole_world_access = lua
154+
.create_function(|lua, ()| {
155+
let world = lua.get_world();
156+
world.claim_global_access();
157+
Ok(())
158+
})
159+
.unwrap();
160+
123161
let globals = lua.globals();
124162
globals
125163
.set(
@@ -131,6 +169,14 @@ fn init_lua_test_utils(_script_name: &Cow<'static, str>, lua: &mut Lua) -> Resul
131169
globals.set("assert_throws", assert_throws).unwrap();
132170

133171
globals.set("_get_mock_type", _get_mock_type).unwrap();
172+
173+
globals
174+
.set("_claim_write_access", set_write_access)
175+
.unwrap();
176+
globals.set("_claim_read_access", set_read_access).unwrap();
177+
globals
178+
.set("_claim_global_access", claim_whole_world_access)
179+
.unwrap();
134180
Ok(())
135181
}
136182

@@ -184,12 +230,6 @@ impl Test {
184230
Failed::from(msg)
185231
})?;
186232

187-
// WorldCallbackAccess::with_callback_access(app.world_mut(), |world| {
188-
// lua.globals().set("world", LuaWorld(world.clone())).unwrap();
189-
190-
// let code = lua.load(self.code).set_name(self.path.to_string_lossy());
191-
// code.exec().map_err(|e| e.to_string())
192-
// })?;
193233
Ok(())
194234
}
195235

0 commit comments

Comments
 (0)