-
Notifications
You must be signed in to change notification settings - Fork 76
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
Comments
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:
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);
}
}
}
-- 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 The |
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 👍 |
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. 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. |
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
where
LuaComponent
is just a proxy of the abstractComponent
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:
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
The text was updated successfully, but these errors were encountered: