bevy_mod_scripting icon indicating copy to clipboard operation
bevy_mod_scripting copied to clipboard

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

Open shanecelis opened this issue 7 months ago • 5 comments

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.

shanecelis avatar Apr 02 '25 08:04 shanecelis

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 avatar Apr 02 '25 17:04 makspll

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

makspll avatar Apr 02 '25 17:04 makspll

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 avatar Apr 02 '25 23:04 shanecelis

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.

shanecelis avatar Apr 03 '25 04:04 shanecelis

Ah I see!

Coincidentally I am working on refactoring the plugin system + adding more fine grained feature flags: https://github.com/makspll/bevy_mod_scripting/pull/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

makspll avatar Apr 03 '25 17:04 makspll

@shanecelis I have given this some love in: https://github.com/makspll/bevy_mod_scripting/pull/478, from then on you will be able to register DisplayWithTypeInfo against arbitrary reflect types to customize how they're printed without registering functions

makspll avatar Sep 08 '25 07:09 makspll

Oh my gosh, dare to dream. Thank you!

shanecelis avatar Sep 09 '25 16:09 shanecelis