Skip to content

Commit defd052

Browse files
committed
WIP
1 parent 6b1f041 commit defd052

File tree

9 files changed

+193
-51
lines changed

9 files changed

+193
-51
lines changed

assets/scripts/bevy_api.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ function on_event()
1919

2020

2121
local my_component_type = world:get_type_by_name("MyComponent")
22+
print("MyComponent type: ", my_component_type:print_value())
2223

2324
local comp = world:get_component(entity, my_component_type)
2425
print("Before script: ", comp:print_value())

crates/bevy_mod_scripting_core/src/bindings/function.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use bevy::reflect::{
88
PartialReflect,
99
};
1010

11-
use crate::error::{InteropError, ScriptError, ScriptResult};
11+
use crate::{
12+
error::{FlattenError, InteropError, InteropErrorInner, ScriptError, ScriptResult},
13+
reflection_extensions::{PartialReflectExt, ReturnValExt},
14+
};
1215

1316
use super::{
1417
access_map::ReflectAccessId,
@@ -86,6 +89,12 @@ impl CallableWithAccess for DynamicFunction<'_> {
8689
final_args_list = final_args_list.push_arg(next_arg);
8790
}
8891

92+
bevy::log::trace!(
93+
"Calling function: {:?} with args: {:?}",
94+
self.info().name(),
95+
final_args_list
96+
);
97+
8998
let return_val = match self.call(final_args_list) {
9099
Ok(return_val) => return_val,
91100
Err(e) => {
@@ -99,6 +108,12 @@ impl CallableWithAccess for DynamicFunction<'_> {
99108
}
100109
};
101110

111+
bevy::log::trace!(
112+
"Function: {:?} returned: {:?}",
113+
self.info().name(),
114+
return_val
115+
);
116+
102117
let out = f(return_val);
103118
// Safety: we have not generated any unsafe aliases
104119
// - we are releasing only the access we have claimed
@@ -114,11 +129,51 @@ impl CallableWithAccess for DynamicFunction<'_> {
114129
args: I,
115130
world: Arc<WorldAccessGuard>,
116131
) -> Result<ScriptValue, InteropError> {
117-
self.with_call(args, world.clone(), |r| match r {
118-
Return::Owned(partial_reflect) => partial_reflect.as_ref().into_script_value(world),
119-
Return::Ref(ref_) => ref_.into_script_value(world),
120-
Return::Mut(mut_ref) => mut_ref.into_script_value(world),
121-
})?
132+
bevy::log::debug!("Dynamic call to function: {:?}", self.info().name());
133+
self.with_call(args, world.clone(), |r| {
134+
let conversion = match r {
135+
Return::Owned(partial_reflect) => {
136+
match partial_reflect.as_ref().into_script_value(world.clone()) {
137+
Err(e)
138+
if matches!(
139+
e.inner(),
140+
InteropErrorInner::BetterConversionExists { .. }
141+
) =>
142+
{
143+
let allocator = world.allocator();
144+
let mut allocator = allocator.write();
145+
ReflectReference::new_allocated_boxed(partial_reflect, &mut allocator)
146+
.into_script_value(world.clone())
147+
}
148+
e => e,
149+
}
150+
}
151+
v => {
152+
let ref_ = v.as_ref();
153+
154+
match ref_.into_script_value(world.clone()) {
155+
Err(e)
156+
if matches!(
157+
e.inner(),
158+
InteropErrorInner::BetterConversionExists { .. }
159+
) =>
160+
{
161+
let val =
162+
<dyn PartialReflect>::from_reflect_or_clone(ref_, world.clone());
163+
let allocator = world.allocator();
164+
let mut allocator = allocator.write();
165+
166+
ReflectReference::new_allocated_boxed(val, &mut allocator)
167+
.into_script_value(world.clone())
168+
}
169+
e => e,
170+
}
171+
}
172+
};
173+
174+
conversion
175+
})
176+
.flatten_interop_error()
122177
.map_err(|e| InteropError::function_interop_error(self.info(), None, e))
123178
}
124179
}

crates/bevy_mod_scripting_core/src/bindings/reference.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{
1212
bindings::{pretty_print::DisplayWithWorld, ReflectAllocationId},
1313
error::InteropError,
1414
prelude::{ReflectAllocator, ScriptResult},
15-
reflection_extensions::PartialReflectExt,
15+
reflection_extensions::{PartialReflectExt, TypeIdExtensions},
1616
with_access_read, with_access_write,
1717
};
1818
use bevy::{
@@ -215,21 +215,8 @@ impl ReflectReference {
215215
}
216216
}
217217

218-
// try from reflect
219-
let type_registry = world.type_registry();
220-
let type_registry = type_registry.read();
221-
222-
let from_reflect: Option<&ReflectFromReflect> =
223-
type_registry.get_type_data(self.base.type_id);
224-
225-
self.with_reflect(world, |r| {
226-
if let Some(from_reflect) = from_reflect {
227-
let from_reflect = from_reflect.from_reflect(r);
228-
if let Some(from_reflect) = from_reflect {
229-
return from_reflect.into_partial_reflect();
230-
}
231-
}
232-
r.clone_value()
218+
self.with_reflect(world.clone(), |r| {
219+
<dyn PartialReflect>::from_reflect_or_clone(r, world.clone())
233220
})
234221
}
235222

crates/bevy_mod_scripting_core/src/bindings/script_value/from.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ impl FromScriptValue for Option<&dyn PartialReflect> {
129129
world: WorldGuard,
130130
target_type_id: TypeId,
131131
) -> Option<Result<Box<dyn PartialReflect>, InteropError>> {
132+
println!("Option<&dyn PartialReflect>::from_script_value {:?}", value);
132133
let type_registry = world.type_registry();
133134
let type_registry = type_registry.read();
134135
let type_info = type_registry.get_type_info(target_type_id)?;

crates/bevy_mod_scripting_core/src/bindings/script_value/into.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ macro_rules! into_script_value_downcast {
3434

3535
impl IntoScriptValue for &dyn PartialReflect {
3636
fn into_script_value(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
37+
bevy::log::trace!("Converting {:?} to ScriptValue", self);
38+
3739
let target_type_id = self
3840
.get_represented_type_info()
3941
.map(|ti| ti.type_id())
@@ -43,6 +45,7 @@ impl IntoScriptValue for &dyn PartialReflect {
4345
// for arbitrary result types we support ScriptValue returns
4446
_ if TypeId::of::<ScriptValue>() == target_type_id => {
4547
match self.try_downcast_ref::<ScriptValue>() {
48+
Some(ScriptValue::Error(e)) => return Err(e.clone()),
4649
Some(script_val) => return Ok(script_val.clone()),
4750
None => {
4851
return Err(InteropError::type_mismatch(
@@ -144,19 +147,20 @@ impl IntoScriptValue for &dyn PartialReflect {
144147
return inner.into_script_value(world);
145148
}
146149

147-
if let Ok(list) = self.as_list() {
148-
let list: Vec<_> = list.collect();
149-
return list.into_script_value(world);
150-
}
150+
// if let Ok(list) = self.as_list() {
151+
// let list: Vec<_> = list.collect();
152+
// return list.into_script_value(world);
153+
// }
151154

152-
// as a last resort we just allocate the value and return a reference to it
153-
let reflect_reference = self.allocate_cloned(world.clone());
154-
ReflectReference::into_script_value(reflect_reference, world)
155+
// this is us saying, we cannot convert this into a nice script value
156+
// you're gonna have to allocate and ref to it
157+
Err(InteropError::better_conversion_exists::<Self>())
155158
}
156159
}
157160

158161
impl IntoScriptValue for Option<&dyn PartialReflect> {
159162
fn into_script_value(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
163+
bevy::log::trace!("Converting Option {:?} to ScriptValue", self);
160164
match self {
161165
Some(inner) => inner.into_script_value(world),
162166
None => Ok(ScriptValue::Unit),

crates/bevy_mod_scripting_core/src/bindings/script_value/mod.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use bevy::reflect::{
1111
};
1212

1313
use crate::{
14-
error::{InteropError, ScriptError, ScriptResult},
14+
error::{InteropError, InteropErrorInner, ScriptError, ScriptResult},
1515
reflection_extensions::{PartialReflectExt, TypeIdExtensions, TypeInfoExtensions},
1616
};
1717

@@ -41,7 +41,7 @@ pub enum ScriptValue {
4141
/// Represents a reference to a value.
4242
Reference(ReflectReference),
4343
/// Represents any error, will be thrown when returned to a script
44-
Error(ScriptError),
44+
Error(InteropError),
4545
/// A placeholder for a [`crate::bindings::WorldCallbackAccess`] value.
4646
World,
4747
}
@@ -100,15 +100,15 @@ impl From<ReflectReference> for ScriptValue {
100100
}
101101
}
102102

103-
impl From<ScriptError> for ScriptValue {
104-
fn from(value: ScriptError) -> Self {
105-
ScriptValue::Error(value)
106-
}
107-
}
103+
// impl From<ScriptError> for ScriptValue {
104+
// fn from(value: ScriptError) -> Self {
105+
// ScriptValue::Error(value)
106+
// }
107+
// }
108108

109109
impl From<InteropError> for ScriptValue {
110110
fn from(value: InteropError) -> Self {
111-
ScriptValue::Error(ScriptError::new(value))
111+
ScriptValue::Error(value)
112112
}
113113
}
114114

@@ -121,7 +121,7 @@ impl<T: Into<ScriptValue>> From<Option<T>> for ScriptValue {
121121
}
122122
}
123123

124-
impl<T: Into<ScriptValue>, E: Into<ScriptError>> From<Result<T, E>> for ScriptValue {
124+
impl<T: Into<ScriptValue>, E: Into<InteropError>> From<Result<T, E>> for ScriptValue {
125125
fn from(value: Result<T, E>) -> Self {
126126
match value {
127127
Ok(v) => v.into(),
@@ -174,9 +174,30 @@ impl TryFrom<ScriptValue> for ParsedPath {
174174
}
175175
}
176176

177-
/// A trait for converting a value into a [`ScriptVal`].
177+
/// A trait for converting a value into a [`ScriptValue`].
178+
///
179+
/// If a [`crate::error::InteropError::better_conversion_exists`] is thrown, the conversion is not possible and you should treat this as a sign to try another method.
178180
pub trait IntoScriptValue {
181+
/// Converts the value into a [`ScriptValue`]. This conversion should:
182+
/// - Ideally convert to a concrete instance of [`Self`] or at least a concrete type representing [`Self`].
183+
/// - If the value is not possible to convert nicely as a value throw a [`crate::error::InteropError::better_conversion_exists`] error so the caller can try another method.
179184
fn into_script_value(self, world: WorldGuard) -> Result<ScriptValue, InteropError>;
185+
186+
/// Some values are better represented as references returned to a script.
187+
/// This method should be called when such values might be returned to a script.
188+
/// By default this will call [`IntoScriptValue::into_script_value`] and convert the underlying [`&dyn PartialReflect`]
189+
/// However if `into_script_value` throws a [`crate::error::InteropError::better_conversion_exists`] error, this method will directly return the reference instead.
190+
fn reference_into_script_value(
191+
self_ref: ReflectReference,
192+
world: WorldGuard,
193+
) -> Result<ScriptValue, InteropError> {
194+
match self_ref.with_reflect(world.clone(), |r| r.into_script_value(world))? {
195+
Err(e) if matches!(e.inner(), InteropErrorInner::BetterConversionExists { .. }) => {
196+
Ok(ScriptValue::Reference(self_ref))
197+
}
198+
e => e,
199+
}
200+
}
180201
}
181202

182203
/// Targeted conversion from a [`ScriptValue`] to a specific type. Can create dynamic types as well as concrete types depending on the implementation.

crates/bevy_mod_scripting_core/src/error.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl From<InteropError> for mlua::Error {
183183
}
184184
}
185185

186-
#[derive(Debug, Clone)]
186+
#[derive(Debug, Clone, PartialEq)]
187187
pub struct InteropError(Arc<InteropErrorInner>);
188188

189189
impl std::error::Error for InteropError {}
@@ -202,6 +202,20 @@ impl From<InteropError> for ScriptError {
202202
}
203203
}
204204

205+
pub trait FlattenError<O,E> {
206+
fn flatten_interop_error(self) -> Result<O, E>;
207+
}
208+
209+
impl <O>FlattenError<O, InteropError> for Result<Result<O,InteropError>, InteropError> {
210+
fn flatten_interop_error(self) -> Result<O, InteropError> {
211+
match self {
212+
Ok(Ok(o)) => Ok(o),
213+
Ok(Err(e)) => Err(e),
214+
Err(e) => Err(e)
215+
}
216+
}
217+
}
218+
205219
impl InteropError {
206220
/// Thrown if a callback requires world access, but is unable to do so due
207221
/// to the world not being reachable at all via any mechanism.
@@ -251,6 +265,16 @@ impl InteropError {
251265
Self(Arc::new(InteropErrorInner::ImpossibleConversion { into }))
252266
}
253267

268+
/// Thrown if a conversion was not fully completed, as a better conversion exists.
269+
/// If a function might throw this error it should be handled by the caller.
270+
///
271+
/// A user seeing this error is evidence of unfinished logic.
272+
pub fn better_conversion_exists<T>() -> Self {
273+
Self(Arc::new(InteropErrorInner::BetterConversionExists{
274+
context: std::any::type_name::<T>().to_string()
275+
}))
276+
}
277+
254278
/// Thrown if a value was expected to be of one type but was of another
255279
pub fn type_mismatch(expected: TypeId, got: Option<TypeId>) -> Self {
256280
Self(Arc::new(InteropErrorInner::TypeMismatch { expected, got }))
@@ -354,6 +378,16 @@ impl InteropError {
354378
pub fn inner(&self) -> &InteropErrorInner {
355379
&self.0
356380
}
381+
382+
/// Unwraps the inner error
383+
///
384+
/// # Panics
385+
/// - if there are multiple references to the inner error
386+
pub fn into_inner(self) -> InteropErrorInner {
387+
Arc::try_unwrap(self.0).unwrap_or_else(|a| {
388+
Arc::try_unwrap(a).expect("tried to unwrap interop error while a copy exists")
389+
})
390+
}
357391
}
358392

359393
impl_dummy_display!(InteropErrorInner);
@@ -380,6 +414,9 @@ pub enum InteropErrorInner {
380414
ImpossibleConversion {
381415
into: TypeId,
382416
},
417+
BetterConversionExists {
418+
context: String
419+
},
383420
TypeMismatch {
384421
expected: TypeId,
385422
got: Option<TypeId>,
@@ -428,9 +465,16 @@ pub enum InteropErrorInner {
428465
},
429466
}
430467

468+
impl PartialEq for InteropErrorInner {
469+
fn eq(&self, other: &Self) -> bool {
470+
false
471+
}
472+
}
473+
431474
impl DisplayWithWorld for InteropErrorInner {
432475
fn display_with_world(&self, world: crate::bindings::WorldGuard) -> String {
433476
match self {
477+
434478
InteropErrorInner::UnregisteredBase { base } => {
435479
format!("Unregistered base type: {}", base.display_with_world(world))
436480
}
@@ -549,6 +593,9 @@ impl DisplayWithWorld for InteropErrorInner {
549593
InteropErrorInner::FunctionCallError { inner } => {
550594
inner.to_string()
551595
},
596+
InteropErrorInner::BetterConversionExists{ context } => {
597+
format!("Unfinished conversion in context of: {}. A better conversion exists but caller didn't handle the case.", context)
598+
},
552599
}
553600
}
554601
}

0 commit comments

Comments
 (0)