Skip to content

Using C# object as a metatable #217

@Charanor

Description

@Charanor

Tl;dr

Assigning the ScriptableDoor as metatable index results in LuaRuntimeException: bad argument #1 to 'index' (ScriptableDoor expected, got Table). This is because the generated class does this in its own metatable indexer context.GetArgument<global::ScriptableDoor>(0):

var csObject = new ScriptableDoor(); // Has [LuaObject] attribute with [LuaMember] members
var table = new LuaTable();
table.Metatable = new LuaTable()
{
    [Lua.Runtime.Metamethods.Index] = csObject
};
state.Environment["obj"] = table;

Then in lua:

-- Exception
obj:OnOpen(nil);

I'm trying to allow users to extend C# objects with Lua code to allow for more object-oriented scripting. For example a scriptable C# object might look like this:

[LuaObject]
public class ScriptableDoor
{
    /* Lua state and stuff saved as well */

    public ScriptableDoor(LuaTable scriptClass)
    {
        // Create LuaObject below, set states, metatables, etc.
    }

    public LuaTable LuaObject { get; }
    public bool IsOpen { get; private set; }

    [LuaMember]
    public void OnCreated()
    {
        // NOOP
    }

    [LuaMember]
    public void OnOpen(Entity entity)
    {
        // Open the door
        this.IsOpen = true
    }

    [LuaMember]
    public void OnClose(Entity entity)
    {
        // Close the door
        this.IsOpen = false
    }
}

Then one could create a script for such a door like this:

-- object_trap_door.lua
object_trap_door = object_trap_door or {}

function object_trap_door:OnCreated()
    self.damage = 3
end

function object_trap_door:OnOpen(entity)
    self.Base:OnOpen(entity)
    entity:DealDamage(self.damage)
end

The idea is that it can then be used like this:

local instance = CreateInstance(object_trap_door)
instance:OnOpen(player) -- Uses OnOpen from the script definition
instance:OnClose(player) -- Uses OnClose from the __index (the C# class).

CreateInstance would look something like:

var state = LuaState.Create();
await state.DoFileAsync("object_trap_door.lua");
var scriptClass = state.Environment["object_trap_door"].Read<LuaTable>();

var csInstance = new ScriptableDoor(scriptClass);
var luaInstance = csInstance.LuaObject;
luaInstance.Metatable = new LuaTable()
{
    [Lua.Runtime.Metamethods.Index] = csInstance,
};
await luaInstance[nameof(ScriptableDoor.OnCreated)].Read<LuaFunction>().InvokeAsync(state, [luaInstance]);

return luaInstance;

However this doesn't work. Assigning the ScriptableDoor as metatable index results in LuaRuntimeException: bad argument #1 to 'index' (ScriptableDoor expected, got Table). This is because the generated class does this in its own metatable indexer context.GetArgument<global::ScriptableDoor>(0).

This could be fixed by creating a custom metatable indexer of course, but outside of reflection there is no convenient way to do this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions