neolua
neolua copied to clipboard
C# method override in Lua
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
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.
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:
- 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);
}
}
}
- 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.
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.
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 :+1:
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
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.