Skip to content
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

How to turn a ReflectedValue into a specfic type? #85

Closed
zwazel opened this issue Nov 8, 2023 · 6 comments
Closed

How to turn a ReflectedValue into a specfic type? #85

zwazel opened this issue Nov 8, 2023 · 6 comments

Comments

@zwazel
Copy link
Contributor

zwazel commented Nov 8, 2023

Following scenario, i get a resource

let lobby_list_type = world.get_type_by_name("blablabla::LobbyListResource");
if world.has_resource(lobby_list_type) {
    let lobby_list = world.get_resource(lobby_list_type);
    print(`lobby list type: ${type_of(lobby_list)}, lobby list: ${lobby_list}`);
}

This resource looks like this:

#[derive(Resource, Reflect, Default, Clone)]
#[reflect(Resource)]
pub struct LobbyListResource(pub Vec<u64>);

Quite simple.
I now want to access the different lobbies from the vec, I figured out that I can just access them by indexing.

print(`lobby list: ${lobby_list[0]}`);

this works.
But I'd like to loop through them:

for lobby in lobby_list {
    print(`lobby: ${lobby}`);
}

Which gives me following error: Runtime error in script "script_name.rhai" For loop expects iterable type.
So I looked into Rhai and how you've implemented some things.
For example, I can use the api.build_type in the attach_api of an APIProvider, and register some functions as well as it being iterable:

#[derive(Resource, Reflect, Default, Clone)]
#[reflect(Resource)]
pub struct LobbyListResource(pub Vec<u64>);

impl IntoIterator for LobbyListResource {
    type Item = u64;

    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl CustomType for LobbyListResource {
    fn build(mut builder: rhai::TypeBuilder<Self>) {
        builder
            .with_name("LobbyListResource")
            .with_indexer_get(LobbyListResource::get_field).is_iterable();
    }
}

struct HandleLobbyApiProvider;

impl APIProvider for HandleLobbyApiProvider {
    type APITarget = Engine;
    type ScriptContext = RhaiContext;
    type DocTarget = RhaiDocFragment;

    fn attach_api(
        &mut self,
        api: &mut Self::APITarget,
    ) -> Result<(), bevy_mod_scripting::prelude::ScriptError> {
        api.build_type::<LobbyListResource>();

        Ok(())
    }
}

But that doesn't solve my problem, because the script only gets a ReflectedValue. So i'm wondering if there is a way for me to turn a ReflectedValue into a specific Type?

@makspll
Copy link
Owner

makspll commented Nov 8, 2023

TL;DR: in your api provider attach_api add:

api.register_vec_functions::<u64>();

then you should be able to simply:

for lobby in lobby_list.0 {
    print(`elem: ${e}`);
}

Hey, you're missing a link between the reflection system and your custom 'proxy'

So the way ReflectedValue works is it looks for registrations of RhaiProxyable traits on the types you're accessing when traversing your type via field accessors, Vec<T> actually already has a custom type implemented for it hooked into via:

RhaiProxyable impl for Vec

impl<T: RhaiVecElem> RhaiProxyable for Vec<T> {
    fn ref_to_rhai(self_: crate::ScriptRef) -> Result<Dynamic, Box<EvalAltResult>> {
        Ok(Dynamic::from(RhaiVec::<T>::new_ref(self_)))
    }
    // ...
}

The logical path from get_resource is as follows (with error paths and other things simplified away for brevity):

 let resource : Option<ScriptRef> = self_.get_resource(res_type)?;
 if let Some(c) = resource {
     c.to_dynamic()
 } else {
     Ok(Default::default())
 }
    pub fn get_resource(
       &self,
       res_type: ScriptTypeRegistration,
   ) -> Result<Option<ScriptRef>, ScriptError> {
       let w = self.read();

       let resource_data = res_type.data::<ReflectResource>()?;

       Ok(resource_data
           .reflect(&w)
           .map(|_res| ScriptRef::new_resource_ref(resource_data.clone(), self.clone().into())))
   }
impl ToDynamic for ScriptRef {
   fn to_dynamic(self) -> Result<Dynamic, Box<EvalAltResult>> {
       let world = self.world_ptr.clone();
       let world = world.read();

       let type_data = world.resource::<AppTypeRegistry>();
       let g = type_data.read();

       let type_id = self.get(|s| s.type_id())?;
       // IMPORTANT
       if let Some(v) = g.get_type_data::<ReflectRhaiProxyable>(type_id) {
           v.ref_to_rhai(self)
       } else {
           ReflectedValue { ref_: self }.to_dynamic()
       }
   }
}
            .with_indexer_get_result(|obj: &mut ReflectedValue, index: Dynamic| {
               obj.ref_.index(index)?.to_dynamic()
           })

So basically the type data corresponding to ReflectRhaiProxyable decides how the value is converted into a Rhai value, if it doesn't exist you get a ReflectedValue type which is the most generic reflection primitive. I believe you're missing the hooks which register the RhaiProxyable traits against your particular types like in the rhai/bevy_api.rs example:

    fn attach_api(&mut self, api: &mut Self::APITarget) -> Result<(), ScriptError> {
       api.set_max_expr_depths(999, 999);
       // one for each type corresponding to T in your Vec<T>
       api.register_vec_functions::<Option<bool>>();
       api.register_vec_functions::<bool>();
       Ok(())
   }

try adding those and see if your value converts to a RhaiVec

@zwazel
Copy link
Contributor Author

zwazel commented Nov 9, 2023

then you should be able to simply:

for lobby in lobby_list.0 {
    print(`elem: ${e}`);
}

I get a Syntax error: Error in loading script lobby_list_container.rhai: Syntax error for script "script_name" Expecting name of a property.
So using .0 does not work, it seems. I changed the resource from a tuple to a struct.
And that works.
But surprisingly, trying to loop through the empty array causes a crash.

if world.has_resource(lobby_list_type) {
    let lobbies = world.get_resource(lobby_list_type).lobbies;
    for lobby in lobbies {
        print(`lobby: ${lobby}`);
    }
}

The Crash error:

thread 'Compute Task Pool (0)' panicked at 'called `Option::unwrap()` on a `None` value', C:\...\.cargo\git\checkouts\bevy_mod_scripting-ff78cea4271e6409\6bafad2\bevy_mod_scripting_core\src\hosts.rs:364:57
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_mod_scripting_core::systems::script_hot_reload_handler<bevy_mod_scripting_rhai::RhaiScriptHost<pgc::modding::ModdedScriptRuntimeArguments>>`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!

Which surprised me, i expected it to just act like a normal for loop and skip it when it's empty.
For now I can workaround by just checking if its empty first:

if world.has_resource(lobby_list_type) {
    let lobbies = world.get_resource(lobby_list_type).lobbies;
    if !lobbies.is_empty() {
        for lobby in lobbies {
            print(`lobby: ${lobby}`);
        }
    }
}

Is this a confirmed bug or intended behaviour? I can open another issue if you want for this.

@zwazel
Copy link
Contributor Author

zwazel commented Nov 9, 2023

I've created a new branch on my local version and will look into it, i already found a place to add on #68. Will create a Pull Request later

@makspll
Copy link
Owner

makspll commented Nov 9, 2023

Hey, indeed looks like a bug, please open another issue for the bug so it's easier to find for others :) thanks a lot!

As for accessing the tuple struct, it might be we need slightly different syntax try with '[0]' or '._0' I'll need to check back what this was for rhai

@makspll
Copy link
Owner

makspll commented Nov 9, 2023

I think I need to get around to building a test tool for all off the common usage patterns of the script APIs

@zwazel
Copy link
Contributor Author

zwazel commented Nov 9, 2023

I've opened issue #86 and will close this one.

@zwazel zwazel closed this as completed Nov 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants