Unverified Commit 4783440b authored by Mathyn's avatar Mathyn Committed by GitHub
Browse files

Moved Lua _globals to a separate class (this class includes a fix for #468) (#469)

* Moved Lua _globals to a separate class (this class includes a fix for #468)

* Added unit tests and made a small tweak to LuaGlobals

* bin and obj for test .net6.0 project are now correctly ignored

* Removed Assert.Contains calls because these are not supported on iOS and tvOS
parent 5bd2527a
...@@ -41,3 +41,5 @@ tests/build/xamarinios/result.xml ...@@ -41,3 +41,5 @@ tests/build/xamarinios/result.xml
tests/build/xamarinios/TEST-Result-Xamarin.iOS.xml tests/build/xamarinios/TEST-Result-Xamarin.iOS.xml
tests/build/xamarintvos/result.xml tests/build/xamarintvos/result.xml
build/net6.0/obj build/net6.0/obj
tests/build/net6.0/obj/
tests/build/net6.0/bin/
...@@ -40,8 +40,7 @@ namespace NLua ...@@ -40,8 +40,7 @@ namespace NLua
private LuaHookFunction _hookCallback; private LuaHookFunction _hookCallback;
#endregion #endregion
#region Globals auto-complete #region Globals auto-complete
private readonly List<string> _globals = new List<string>(); private readonly LuaGlobals _globals = new LuaGlobals();
private bool _globalsSorted;
#endregion #endregion
private LuaState _luaState; private LuaState _luaState;
/// <summary> /// <summary>
...@@ -218,7 +217,17 @@ namespace NLua ...@@ -218,7 +217,17 @@ namespace NLua
/// <summary> /// <summary>
/// The maximum number of recursive steps to take when adding global reference variables. Defaults to 2. /// The maximum number of recursive steps to take when adding global reference variables. Defaults to 2.
/// </summary> /// </summary>
public int MaximumRecursion { get; set; } = 2; public int MaximumRecursion
{
get
{
return _globals.MaximumRecursion;
}
set
{
_globals.MaximumRecursion = value;
}
}
#region Globals auto-complete #region Globals auto-complete
/// <summary> /// <summary>
...@@ -228,14 +237,7 @@ namespace NLua ...@@ -228,14 +237,7 @@ namespace NLua
public IEnumerable<string> Globals { public IEnumerable<string> Globals {
get get
{ {
// Only sort list when necessary return _globals.Globals;
if (!_globalsSorted)
{
_globals.Sort();
_globalsSorted = true;
}
return _globals;
} }
} }
#endregion #endregion
...@@ -626,13 +628,13 @@ namespace NLua ...@@ -626,13 +628,13 @@ namespace NLua
if (value == null) if (value == null)
{ {
// Remove now obsolete entries // Remove now obsolete entries
_globals.Remove(fullPath); _globals.RemoveGlobal(fullPath);
} }
else else
{ {
// Add new entries // Add new entries
if (!_globals.Contains(fullPath)) if (!_globals.Contains(fullPath))
RegisterGlobal(fullPath, value.GetType(), 0); _globals.RegisterGlobal(fullPath, value.GetType(), 0);
} }
} }
/* /*
...@@ -654,89 +656,6 @@ namespace NLua ...@@ -654,89 +656,6 @@ namespace NLua
} }
} }
#region Globals auto-complete
/// <summary>
/// Adds an entry to <see cref = "_globals"/> (recursivley handles 2 levels of members)
/// </summary>
/// <param name = "path">The index accessor path ot the entry</param>
/// <param name = "type">The type of the entry</param>
/// <param name = "recursionCounter">How deep have we gone with recursion?</param>
private void RegisterGlobal(string path, Type type, int recursionCounter)
{
// If the type is a global method, list it directly
if (type == typeof(LuaFunction))
{
// Format for easy method invocation
_globals.Add(path + "(");
}
// If the type is a class or an interface and recursion hasn't been running too long, list the members
else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < MaximumRecursion)
{
#region Methods
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
string name = method.Name;
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!method.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) &&
// Exclude some generic .NET methods that wouldn't be very usefull in Lua
name != "GetType" && name != "GetHashCode" && name != "Equals" &&
name != "ToString" && name != "Clone" && name != "Dispose" &&
name != "GetEnumerator" && name != "CopyTo" &&
!name.StartsWith("get_", StringComparison.Ordinal) &&
!name.StartsWith("set_", StringComparison.Ordinal) &&
!name.StartsWith("add_", StringComparison.Ordinal) &&
!name.StartsWith("remove_", StringComparison.Ordinal))
{
// Format for easy method invocation
string command = path + ":" + name + "(";
if (method.GetParameters().Length == 0)
command += ")";
_globals.Add(command);
}
}
#endregion
#region Fields
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()))
{
// Go into recursion for members
RegisterGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1);
}
}
#endregion
#region Properties
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any())
// Exclude some generic .NET properties that wouldn't be very useful in Lua
&& property.Name != "Item")
{
// Go into recursion for members
RegisterGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1);
}
}
#endregion
}
else
_globals.Add(path); // Otherwise simply add the element to the list
// List will need to be sorted on next access
_globalsSorted = false;
}
#endregion
/* /*
* Navigates a table in the top of the stack, returning * Navigates a table in the top of the stack, returning
* the value of the specified field * the value of the specified field
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NLua
{
public class LuaGlobalEntry
{
/// <summary>
/// Type at time of registration.
/// </summary>
public Type Type { get; private set; }
public string Path { get; private set; }
/// <summary>
/// List of global properties 'owned' by this entry.
/// If this entry is removed all these globals should be removed as well.
/// </summary>
public List<string> linkedGlobals = new List<string>();
public LuaGlobalEntry(Type type, string path)
{
Type = type;
Path = path;
}
}
public class LuaGlobals
{
private List<string> _globals = new List<string>();
private List<LuaGlobalEntry> _knownTypes = new List<LuaGlobalEntry>();
public bool _globalsSorted = false;
public int MaximumRecursion { get; set; } = 2;
public IEnumerable<string> Globals
{
get
{
// Only sort list when necessary
if (!_globalsSorted)
{
_globals.Sort();
_globalsSorted = true;
}
return _globals;
}
}
public bool Contains(string fullPath)
{
return _globals.Contains(fullPath);
}
public void RemoveGlobal(string path)
{
var knownType = GetKnownType(path);
if (knownType != null)
{
// We need to clean up the globals
foreach (var dependent in knownType.linkedGlobals)
{
_globals.Remove(dependent);
}
_knownTypes.Remove(knownType);
}
}
private LuaGlobalEntry GetKnownType(string path)
{
return _knownTypes.Find(x => x.Path.Equals(path));
}
public void RegisterGlobal(string path, Type type, int recursionCounter)
{
var knownType = GetKnownType(path);
if (knownType != null)
{
if (type.Equals(knownType.Type))
{
// Object is set to same value so no need to update
return;
}
// Path changed type so we should clean up all known globals associated with the type
RemoveGlobal(path);
}
RegisterPath(path, type, recursionCounter);
// List will need to be sorted on next access
_globalsSorted = false;
}
private void RegisterPath(string path, Type type, int recursionCounter, LuaGlobalEntry entry = null)
{
// If the type is a global method, list it directly
if (type == typeof(LuaFunction))
{
RegisterLuaFunction(path, entry);
}
// If the type is a class or an interface and recursion hasn't been running too long, list the members
else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < MaximumRecursion)
{
RegisterClassOrInterface(path, type, recursionCounter, entry);
}
else
{
RegisterPrimitive(path, entry);
}
}
private void RegisterLuaFunction(string path, LuaGlobalEntry entry = null)
{
// Format for easy method invocation
_globals.Add(path + "(");
if (entry != null)
{
entry.linkedGlobals.Add(path);
}
}
private void RegisterPrimitive(string path, LuaGlobalEntry entry = null)
{
_globals.Add(path);
if (entry != null)
{
entry.linkedGlobals.Add(path);
}
}
private void RegisterClassOrInterface(string path, Type type, int recursionCounter, LuaGlobalEntry entry = null)
{
if (entry == null)
{
entry = new LuaGlobalEntry(type, path);
_knownTypes.Add(entry);
}
#region Methods
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
string name = method.Name;
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!method.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) &&
// Exclude some generic .NET methods that wouldn't be very usefull in Lua
name != "GetType" && name != "GetHashCode" && name != "Equals" &&
name != "ToString" && name != "Clone" && name != "Dispose" &&
name != "GetEnumerator" && name != "CopyTo" &&
!name.StartsWith("get_", StringComparison.Ordinal) &&
!name.StartsWith("set_", StringComparison.Ordinal) &&
!name.StartsWith("add_", StringComparison.Ordinal) &&
!name.StartsWith("remove_", StringComparison.Ordinal))
{
// Format for easy method invocation
string command = path + ":" + name + "(";
if (method.GetParameters().Length == 0)
command += ")";
_globals.Add(command);
entry.linkedGlobals.Add(command);
}
}
#endregion
#region Fields
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()))
{
// Go into recursion for members
RegisterPath(path + "." + field.Name, field.FieldType, recursionCounter + 1, entry);
}
}
#endregion
#region Properties
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any())
// Exclude some generic .NET properties that wouldn't be very useful in Lua
&& property.Name != "Item")
{
// Go into recursion for members
RegisterPath(path + "." + property.Name, property.PropertyType, recursionCounter + 1, entry);
}
}
#endregion
}
}
}
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
<Compile Include="$(MSBuildThisFileDirectory)LuaBase.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaFunction.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaFunction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaGlobalAttribute.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaGlobalAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaGlobals.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaHideAttribute.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaHideAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaRegistrationHelper.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaRegistrationHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaThread.cs" /> <Compile Include="$(MSBuildThisFileDirectory)LuaThread.cs" />
......
using System; using System;
using System.Text; using System.Text;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
...@@ -983,6 +983,71 @@ namespace NLuaTest ...@@ -983,6 +983,71 @@ namespace NLuaTest
} }
} }
///* ///*
// * Tests setting of a global variable to a CLR object and checking if the Globals correctly registered everything.
// */
[Test]
public void SetGlobalObjectLuaGlobalsListIsCorrect()
{
using (Lua lua = new Lua())
{
lua["netobj"] = new TestTypes.GlobalsTestClass();
var globals = lua.Globals.ToList();
Assert.AreEqual(globals.Count, 4);
Assert.True(globals.Contains("netobj.Property1"));
Assert.True(globals.Contains("netobj.Property2"));
Assert.True(globals.Contains("netobj:Method1()"));
Assert.True(globals.Contains("netobj:Method3("));
}
}
///*
// * Tests setting of a global variable to a CLR object value and then re assigning it to a non CLR object.
// */
[Test]
public void SetGlobalObjectAndReasignToNonCLR()
{
using (Lua lua = new Lua())
{
lua["netobj"] = new TestTypes.GlobalsTestClass();
lua["netobj"] = 4;
var globals = lua.Globals.Where(x => x.StartsWith("netobj")).ToList();
Assert.AreEqual(1, globals.Count);
Assert.True(globals.Contains("netobj"));
}
}
///*
// * Tests setting of a global variable to a CLR object value and then re assigning it to a null value.
// */
[Test]
public void SetGlobalObjectAndReasignToNull()
{
using (Lua lua = new Lua())
{
lua["netobj"] = new TestTypes.GlobalsTestClass();
lua["netobj"] = null;
var globals = lua.Globals.Where(x => x.StartsWith("netobj")).ToList();
Assert.AreEqual(0, globals.Count);
}
}
///*
// * Tests setting of a global variable to a CLR object value and then re assigning it to another CLR object of another type.
// */
[Test]
public void SetGlobalObjectAndReasignToOtherCLR()
{
using (Lua lua = new Lua())
{
lua["netobj"] = new TestTypes.TestClass();
lua["netobj"] = new TestTypes.GlobalsTestClass();
var globals = lua.Globals.Where(x => x.StartsWith("netobj")).ToList();
Assert.AreEqual(4, globals.Count);
Assert.True(globals.Contains("netobj.Property1"));
Assert.True(globals.Contains("netobj.Property2"));
Assert.True(globals.Contains("netobj:Method1()"));
Assert.True(globals.Contains("netobj:Method3("));
}
}
///*
// * Tests if CLR object is being correctly collected by Lua // * Tests if CLR object is being correctly collected by Lua
// */ // */
[Test] [Test]
......
...@@ -50,5 +50,6 @@ ...@@ -50,5 +50,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Properties\AssemblyInfo.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Properties\AssemblyInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\Vector.cs" /> <Compile Include="$(MSBuildThisFileDirectory)TestTypes\Vector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\VectorExtension.cs" /> <Compile Include="$(MSBuildThisFileDirectory)TestTypes\VectorExtension.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\GlobalsTestClass.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
namespace NLuaTest.TestTypes
{
class GlobalsTestClass
{
public int Property1 { get; set; }
public int Property2{ get; }
public int Method1()
{
return 1;
}
private int Method2()
{
return 2;
}
public int Method3(int param)
{
return param;
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment