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

C# method override in Lua #102

Open
na2axl opened this issue Aug 1, 2019 · 6 comments
Open

C# method override in Lua #102

na2axl opened this issue Aug 1, 2019 · 6 comments

Comments

@na2axl
Copy link

na2axl commented Aug 1, 2019

NeoLua Version: 5.3 (Nuget 1.3.10)

Hi, I'm just starting to use NeoLua, which is pretty good, and I fall into an issue... I want to know if it's possible to override a virtual method defined in C# from Lua.

I've tried to do like this

Component = clr.AlienEngine.Core.Gaming.LuaComponent;
cmp = Component();

cmp.Update = function()
    print "Updated";
end;

where LuaComponent is just a proxy of the abstract Component class for the use in lua code.

When I want to execute this code, I got the error 'LuaComponent:Update' is not writable.

I've also tried to do:

Component = clr.AlienEngine.Core.Gaming.LuaComponent;
cmp = Component();

function cmp:Update()
    print "Updated";
end;

and I got the error No conversion defined from LuaComponent to LuaTable.

So please I want to know if it's possible or if there is another way to achieve this goal.
Thanks

@neolithos
Copy link
Owner

If you inherit the proxy from LuaTable, you get the support to do such things.

Just define a property in this form:

[LuaMember("Update")]
private Action LuaUpdate => new Action(Update);

This is a property with an initial value, that can be changed in the script.

@na2axl
Copy link
Author

na2axl commented Aug 5, 2019

Hi neolithos,

Sorry but your solution can't be implemented with the current architecture of our game engine... The goal we want to achieve is to create an inheritable class from lua. So we have found an alternative to do this completly in lua, I will drop our code and some examples here if it can help someone:

The fuction to inherit from C# class in Lua

--- Function used to create a Lua class which inherit a native C# class
-- @param type class The C# class ocject
-- @param object overridable The list of overridable methods from the C# class
function __native_inherit(native, overridable)
    local mt = {}

    local ot = {
        inner = {}
    }

    -- Method/Property set
    function mt:__newindex(key : string, value) : object
        -- Check if we are trying to overwrite an existing key
        if self.inner[key] then
            goto __set_inner__
        end

        -- Check if we are trying to overload a native method
        if type(value) == "function" then
            for _, func in ipairs(overridable) do
                if key == func then
                    native.Bridge:setProperty("Lua" .. key, cast(object, value))
                    goto __return__
                end
            end
        end

        -- Check if we are trying to set a native property
        if native.Bridge:hasProperty(key) then
            native.Bridge:setProperty(key, cast(object, value))
            goto __return__
        end

        -- We are surely creating a new custom value
        ::__set_inner__::
        rawset(self.inner, key, value)

        ::__return__::
        return self
    end

    -- Method/property get
    function mt:__index(key : string) : object
        -- Check if it's a custom member
        if self.inner[key] then
            return self.inner[key]
        end

        -- Check if it's a native property access
        if native.Bridge:hasProperty(key) then
            return native.Bridge:getProperty(key)
        end

        -- Check if it's a native method call
        if native.Bridge:hasMethod(key) then
            return function(obj, ...) : object
                return native.Bridge:callMethod(key, ...)
            end
        end

        return nil
    end

    -- Returns the native C# instance
    function ot:toNative() : object
        return native
    end

    ot.base = native;

    -- Define the metatable
    setmetatable(ot, mt)

    -- Save the lua class instance in C#
    native.Bridge:setSelf(ot)

    return ot
end

The Lua-to-C# bridge:

using System;
using System.Reflection;
using AlienEngine.Core.Scripting.Lua.Interpreter;

namespace AlienEngine.Core.Scripting
{
    public class ScriptBridge<T>
        where T : class
    {
        private static PropertyInfo[] ReflectionProperties => typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        private static MethodInfo[] ReflectionMethods => typeof(T)
            .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

        private LuaTable _self;

        private T _that;

        public LuaTable Self => _self;

        public ScriptBridge(T that)
        {
            _that = that;
        }

        public bool hasProperty(string name)
        {
            foreach (PropertyInfo property in ReflectionProperties)
            {
                if (property.Name == name)
                {
                    return true;
                }
            }

            return false;
        }

        public void setProperty(string name, object value)
        {
            foreach (PropertyInfo property in ReflectionProperties)
            {
                if (property.Name == name)
                {
                    property.SetValue(_that, value);
                    break;
                }
            }
        }

        public object getProperty(string name)
        {
            foreach (PropertyInfo property in ReflectionProperties)
            {
                if (property.Name == name)
                {
                    return property.GetValue(_that);
                }
            }

            return null;
        }

        public bool hasMethod(string name)
        {
            foreach (MethodInfo method in ReflectionMethods)
            {
                if (method.Name == name)
                {
                    return true;
                }
            }

            return false;
        }

        public object callMethod(string name, params object[] args)
        {
            // Execute the method
            return getMethod(name, args)?.Invoke(_that, args);
        }

        public MethodInfo getMethod(string name, params object[] args)
        {
            foreach (MethodInfo method in ReflectionMethods)
            {
                if (method.Name == name)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == args.Length)
                    {
                        bool valid = true;
                        foreach (ParameterInfo parameter in parameters)
                        {
                            if (parameter.ParameterType != args.GetType())
                            {
                                valid = false;
                                break;
                            }
                        }

                        if (valid)
                            return method;
                    }
                }
            }

            return null;
        }

        public void setSelf(LuaTable self)
        {
            _self = self;
        }
    }
}

And the example of use:

  1. The C# class to inherit in Lua
using System;
using AlienEngine.Core.Scripting;

namespace AlienEngine.Core.Gaming
{
    public class LuaComponent : Component
    {
        public ScriptBridge<LuaComponent> Bridge { get; }

        public Action<object> LuaLoad { get; set; }

        public Action<object> LuaStart { get; set; }

        public Action<object> LuaBeforeUpdate { get; set; }

        public Action<object> LuaUpdate { get; set; }

        public Action<object> LuaAfterUpdate { get; set; }

        public Action<object> LuaStop { get; set; }

        public Action<object> LuaUnload { get; set; }

        public Action<object, bool> LuaDispose { get; set; }

        public LuaComponent()
        {
            Bridge = new ScriptBridge<Component>(this);
        }

        public override void Load()
        {
            LuaLoad?.Invoke(Bridge.Self);
        }

        public override void Start()
        {
            LuaStart?.Invoke(Bridge.Self);
        }

        public override void BeforeUpdate()
        {
            LuaBeforeUpdate?.Invoke(Bridge.Self);
        }

        public override void Update()
        {
            LuaUpdate?.Invoke(Bridge.Self);
        }

        public override void AfterUpdate()
        {
            LuaAfterUpdate?.Invoke(Bridge.Self);
        }

        public override void Stop()
        {
            LuaStop?.Invoke(Bridge.Self);
        }

        public override void Unload()
        {
            LuaUnload?.Invoke(Bridge.Self);
        }

        protected override void Dispose(bool disposing)
        {
            LuaDispose?.Invoke(Bridge.Self, disposing);
        }
    }
}
  1. The Lua class which inherit the C# one
-- Types definition
const component typeof AlienEngine.Core.Gaming.LuaComponent;
const camera typeof AlienEngine.Camera;

--- This will reproduce the abstract Component class in C#
--- @class Component
function Component()
    return __native_inherit(
        component(),
        {
            "Load",
            "Start",
            "BeforeUpdate",
            "Update",
            "AfterUpdate",
            "Stop",
            "Unload",
            "Dispose"
        }
    )
end

--- This is a Lua class which inherit from the Component class in C#
--- @class MyComponent
function MyComponent()
    local obj = Component()

    function obj:Start()
        print "MyComponent started"
        obj.camera = obj:GetComponent[camera]()
    end

    function obj:Update()
        print "My component updated"
        if obj.camera.IsPrimary then
            obj.camera:Pitch(0.1)
        end
    end

    function obj:Stop()
        print "MyComponent stopped"
    end

    return obj
end

You can close this issue.

@neolithos
Copy link
Owner

One hint (or IMHO), I would developed this meta-table in c#. I recomment to have a look in the overrides of the class LuaTable. This can make it muhc more easier.

The ScriptBridge<T> and the __native_inherited_ can combined to one implementation.
Inherit ScriptBridge<T> from LuaTable and override OnNewIndex, OnIndex... And do the same stuff you did in the lua part.

@na2axl
Copy link
Author

na2axl commented Aug 5, 2019

Thanks @neolithos for your tip, I'm currently checking for this possibility and I will back to you later. Thanks again for your awesome work 👍

@na2axl
Copy link
Author

na2axl commented Aug 9, 2019

Hi @neolithos, sorry for the late response. Few days ago, I've implemented a solution following your advice, it's totally more clean now and everything is managed from C#, thanks again. The new implementation is:

The new ScriptBridge class:

namespace AlienEngine.Core.Scripting.Lua
{
    public interface IBridgeClass
    {
        LuaTable Self { get; set; }
    }
}
namespace AlienEngine.Core.Scripting
{
    public class ScriptBridge<T> : LuaTable
        where T : class, IBridgeClass
    {
        private static readonly BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        private static readonly PropertyInfo[] ReflectionProperties = typeof(T).GetProperties(BindingFlag);

        private static readonly MethodInfo[] ReflectionMethods = typeof(T).GetMethods(BindingFlag);

        private T _that;

        private LuaTable _inner;

        private List<string> _overridable;

        public ScriptBridge(T that, List<string> overridable)
        {
            _that = that;
            _overridable = overridable;

            _inner = new LuaTable();

            _that.Self = this;
        }

        protected override bool OnNewIndex(object key, object value)
        {
            // Check if we are trying to overwrite an existing key
            if (_inner.ContainsKey(key))
                goto SetInner;

            string memberName = key as string;

            if (memberName != null)
            {
                // Check if we are trying to overload a native method
                if (_overridable.Contains(memberName))
                {
                    _setProperty($"Lua{memberName}", value);
                    goto Return;
                }

                // Check if we are trying to redefine a property
                if (_hasProperty(memberName))
                {
                    _setProperty(memberName, value);
                    goto Return;
                }
            }

SetInner:
            _inner[key] = value;

Return:
            return true;
        }

        protected override object OnIndex(object key)
        {
            // Check if it's a custom member
            if (_inner.ContainsKey(key))
                return _inner[key];

            string memberName = key as string;

            if (memberName == null)
                return null;

            // Check if it's a native property access
            if (_hasProperty(memberName))
                return _getProperty(memberName);

            // Check if it's a native method call
            if (_hasMethod(memberName))
                return new Func<LuaTable, object[], object>((obj, args) => _callMethod(memberName, args));

            return null;
        }

        private bool _hasProperty(string name)
        {
            foreach (PropertyInfo property in ReflectionProperties)
            {
                if (property.Name == name)
                {
                    return true;
                }
            }

            return false;
        }

        private void _setProperty(string name, object value)
        {
            typeof(T).GetProperty(name, BindingFlag)?.SetValue(_that, value);
        }

        private object _getProperty(string name)
        {
            return typeof(T).GetProperty(name, BindingFlag)?.GetValue(_that);
        }

        private bool _hasMethod(string name)
        {
            foreach (MethodInfo method in ReflectionMethods)
            {
                if (method.Name == name)
                {
                    return true;
                }
            }

            return false;
        }

        private object _callMethod(string name, params object[] args)
        {
            // Execute the method
            return _getMethod(name, args)?.Invoke(_that, args);
        }

        private MethodInfo _getMethod(string name, params object[] args)
        {
            return typeof(T).GetMethod
            (
                name,
                BindingFlag,
                Type.DefaultBinder,
                args.Select(arg => arg.GetType()).ToArray(),
                null
            );
        }
    }
}

And the new way to use this:

namespace AlienEngine.Core.Gaming
{
    internal class LuaComponentInternal : Component, IBridgeClass
    {
        public LuaTable Self { get; set; }

        public Action<object> LuaLoad { get; set; }

        public Action<object> LuaStart { get; set; }

        public Action<object> LuaBeforeUpdate { get; set; }

        public Action<object> LuaUpdate { get; set; }

        public Action<object> LuaAfterUpdate { get; set; }

        public Action<object> LuaStop { get; set; }

        public Action<object> LuaUnload { get; set; }

        public Action<object, bool> LuaDispose { get; set; }

        public override void Load()
        {
            LuaLoad?.Invoke(Self);
        }

        public override void Start()
        {
            LuaStart?.Invoke(Self);

            base.Start();
        }

        public override void BeforeUpdate()
        {
            LuaBeforeUpdate?.Invoke(Self);
        }

        public override void Update()
        {
            LuaUpdate?.Invoke(Self);
        }

        public override void AfterUpdate()
        {
            LuaAfterUpdate?.Invoke(Self);
        }

        public override void Stop()
        {
            LuaStop?.Invoke(Self);

            base.Stop();
        }

        public override void Unload()
        {
            LuaUnload?.Invoke(Self);
        }

        protected override void Dispose(bool disposing)
        {
            LuaDispose?.Invoke(Self, disposing);
        }
    }

    public class LuaComponent : LuaTable
    {
        protected override LuaResult OnCall(object[] args)
        {
            return new LuaResult
            (
                new ScriptBridge<LuaComponentInternal>
                (
                    new LuaComponentInternal(),
                    new List<string>
                    {
                        "Load",
                        "Start",
                        "BeforeUpdate",
                        "Update",
                        "AfterUpdate",
                        "Stop",
                        "Unload",
                        "Dispose"
                    }
                )
            );
        }
    }
}
Component = clr.AlienEngine.Core.Gaming.LuaComponent()

function LuaTestComponent()
    local cmp = Component()

    cmp.Name = "LuaTestComponent"

    function cmp:Start() : void
        print ("Component " .. self.Name .. " started")
    end

    function cmp:Update() : void
        print ("\tComponent " .. self.Name .. " updated")
    end

    function cmp:Stop() : void
        print ("Component " .. self.Name .. " stoped")
    end

    return cmp
end

@neolithos
Copy link
Owner

neolithos commented Aug 9, 2019

That looks good.
Just for the list _overrides had choose a more generic way.

Good work. I leave this issue open for other users. May be some times, I or someone other will create a wiki page for this example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants