Skip to content

What to implement for a type to get tostring() or concatenate functionality? #407

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

Open
shanecelis opened this issue Apr 2, 2025 · 5 comments

Comments

@shanecelis
Copy link
Contributor

Problem

I'd like to be able to pass Entity around in Lua, and I can with Val<Entity>, but I'd also like to be able to call tostring() on that value, but what happens currently is this.

Example for tostring

For this code:

world.info("entity "..tostring(e))
2025-04-02T07:56:25.127878Z ERROR bevy_mod_scripting_core::handler: error in script `main.lua`: Could not find function: display_ref for type: bevy_mod_scripting_core::bindings::reference::ReflectReference.
Context:
stack traceback:
        [C]: in ?
        [C]: in function 'tostring'
        [string "/Users/shane/Projects/bevy_mod_scripting/crat..."]:93: in function '_update'
Event handling for: Language: Lua

Example for concatenation

And for concatenation I get this.

world.info("entity "..e)
2025-04-02T08:01:09.663853Z ERROR bevy_mod_scripting_core::handler: error in script `main.lua`: runtime error: [string "/Users/shane/Projects/bevy_mod_scripting/crat..."]:94: attempt to concatenate a LuaReflectReference value (local 'e')
stack traceback:
        [C]: in metamethod 'concat'
        [string "/Users/shane/Projects/bevy_mod_scripting/crat..."]:94: in function '_update'.
Context:
Event handling for: Language: Lua

I'm happy to wrap this value in a custom time struct MyEntity(Entity).

Solution

I'd like for either Debug or Display to be available in Lua's tostring() and concatenate functionality.

Alternative Solution

I can pass the value to myself, and register a entity_tostring() method implemented in Rust do what I want, but that feels off.

@makspll
Copy link
Owner

makspll commented Apr 2, 2025

Hmm, strange! display_ref is defined on ReflectReference level meaning it should be available, the fact it is not found is causing some of the issues here. For instance, running this:

local entity = world.spawn()
print(tostring(entity))
print(entity)

works fine and prints:

<Reference to Allocation(313)(bevy_ecs::entity::Entity) -> bevy_ecs::entity::Entity>
<Reference to Allocation(313)(bevy_ecs::entity::Entity) -> bevy_ecs::entity::Entity>

however trying:

print("a " .. entity)

results in:

error in script `tests/to_string/entity_to_string.lua`: runtime error: [string "crates/languages/bevy_mod_scripting_lua/src/l..."]:4: attempt to concatenate a LuaReflectReference value (local 'entity')

which I believe to be expected behaviour in Lua, but do correct me if I am wrong, an alternative is using format strings:

local entity = world.spawn()
print(string.format("I am an entity: %s.", entity))

which prints:

I am an entity: <Reference to Allocation(313)(bevy_ecs::entity::Entity) -> bevy_ecs::entity::Entity>.

@makspll
Copy link
Owner

makspll commented Apr 2, 2025

I am not sure why you do not have a display_ref function registration available, that should come with the ScriptFunctionsPlugin

@shanecelis
Copy link
Contributor Author

Thank you. That's helpful to know. Hmm, I may not have added that plugin. I was trying to do it without taking in the whole Bevy Lua API. I'll try with it and see if that resolves the issue.

Without that plugin for my own custom struct, how could I achieve the tostring behavior?

@shanecelis
Copy link
Contributor Author

I added the plugin and it behaves better! Given this code:

local e = ent(id) -- e is a Val<Entity>
print_ent(e) -- my work-around.
world.info("entity tostring "..tostring(e))
-- world.info("entity "..e) -- doesn't work, probably bad Lua.
world.info("entity display_ref "..e:display_ref())
world.info("entity display_value "..e:display_value())
world.info("entity "..tostr(e._1))

Here's the log:

2025-04-03T03:57:17.608927Z  INFO nano_9::pico8::lua: print id 234v1
2025-04-03T03:57:17.609018Z  INFO nano_9::plugin: s="entity tostring <Reference to Allocation(1517)(bevy_ecs::entity::Entity) -> bevy_ecs::entity::Entity>"
2025-04-03T03:57:17.609061Z  INFO nano_9::plugin: s="entity display_ref <Reference to Allocation(1517)(bevy_ecs::entity::Entity) -> bevy_ecs::entity::Entity>"
2025-04-03T03:57:17.609130Z  INFO nano_9::plugin: s="entity display_value Reflect(bevy_ecs::entity::Entity)"
2025-04-03T03:57:17.609529Z ERROR bevy_mod_scripting_core::handler: error in script `main.lua`: Error while reflecting path: Error accessing element with `.0` access(offset 1): Expected index access to access a tuple, found a opaque instead. on reference: <Reference to Allocation(1517)(bevy_ecs::entity::Entity).0>.
Context:
stack traceback:
        [C]: in metamethod 'index'
        [string "/Users/shane/Projects/bevy_mod_scripting/crat..."]:98: in function '_update'
Event handling for: Language: Lua

The above code all works except the last line with e._1. I wasn't really expecting it to work, but you can see that the information for e:display_value() is "Reflect(bevy_ecs::entity::Entity)" where I was hoping to get "234v1". Any advice?

I'm happy to write a custom type that will do its own Lua __tostring method. Should I be looking to mlua directly instead of bevy_mod_scripting for that?

Thanks for your help.

@makspll
Copy link
Owner

makspll commented Apr 3, 2025

Ah I see!

Coincidentally I am working on refactoring the plugin system + adding more fine grained feature flags: #408.

As for printing say the index you can use entity:index() you can't use _1 because it's a reflect(opaque) type.

In general though if you wanted to change how types behave on __tostring you have the option of remove'ing the display_ref script function from the registry, and registering your own, however it will have to work for all possible ReflectReference's.

I am planning on eventually improving the printing in general, in this case for example, we could hardcode an override for Entity since it's such a common type. On top of that we could make the __tostring method look for display_ref overrides on the type first before looking at the ReflectReference namespace, allowing per-type overrides at a slight cost to printing performance.

With the way things are implemented, you can't really have a custom mlua type as everything is either represented as a lua primitive, or a LuaReflectReference for all Val/Ref/Mut types, which is what allows all the conversions + calls to work nicely

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