如何使用 MoonSharp 加载 lua table?

How can I use MoonSharp to load a lua table?

在浏览了各种 Lua C# 解释器之后,似乎只有一个是真正纯 C# - MoonSharp。 LuaInterpreter(自 2009 年起失效)后来成为 NLua 取决于另外两个 c# 库管理器 KeraLua 之一或另一个库,并且需要自定义的 lua52.dll(您可以不要使用 lua.org 中的那个)。他们有一个已关闭的错误报告,上面写着查看自述文件以获取他们定制的 lua52.dll 的下载位置,但它不存在。您被迫从各种来源下载这些库,并祈祷它们一起工作,此外还有一个多文件分发,由于最终用户计算机上的几个 lua52.dll 变体,这可能 have/cause 与其他程序的兼容性问题(假设他们会使用的不仅仅是你的程序)。

NLua 上的一盏灯塔显然很受欢迎,但是该项目几年来没有收到任何重大更新。另一方面,MoonSharp 似乎是完全独立的,但缺少常见任务的文档,例如加载使用 lua 构建的 table 并使用它。

我根据他们在 Git 上提供的单个示例提出了以下代码,然后在他们的网站 moonsharp.org 上复制了代码(我不确定,以先到者为准,但是1 个示例是不够的):

using System;
using System.IO;
using MoonSharp.Interpreter;

class Foo {

       function Bar( string accountName, string warcraftPath ) {
             string datastore = Path.Combine(warcraftPath, "WTF", "Account", accountName, "SavedVariables", "DataStore_Containers.lua";

             DynValue table = Script.RunString( File.ReadAllText( datastore ) );

             Console.WriteLine( table.Table.Keys.Count().ToString() );
       }
}

结果如下(图片中的代码略有不同,因为我在这里调整了粘贴的代码以保持整洁,并使您更容易使用 pastebin 中的 table 数据重现问题 link 下面。)

我正在尝试阅读的 table 如下所示(由于大小超过 30,000 个字符,简化后不得不粘贴到 pastebin 上):

World of Warcraft - Datastore_Containers Lua table sample data

我有点工作,有点老套,但似乎并没有离开循环遍历值或显式获取子tables /值或值的键.

    Script s = new Script(CoreModules.Preset_Complete);

    // hacked by appending ' return DataStore_ContainersDB ' to return the table as DoString seems to only work to run a function expecting a result to be returned.
    DynValue dv = s.DoString(luaTable + "\nreturn DataStore_ContainersDB;");
    Table t = dv.Table;
    foreach(var v in t.Keys)
    {
        Console.WriteLine( v.ToPrintString() );
    }

问题是我似乎没有任何方法可以输入子 table 结果集或显式访问像 t["global"]t.global 这样的结果集。

设法破解并削减我的方式并提出一个可行的解决方案,尽管它相当初级(可能有人可以采用这个概念并使子数据的访问更合理:

Script s = new Script(CoreModules.Preset_Complete);
DynValue dv = s.DoString(luaTable + "\nreturn DataStore_ContainersDB;");
Table t =  dv.Table;
Table global;
global = t.Get("global").ToObject<Table>().Get("Characters").ToObject<Table>();

foreach (var key in global.Keys)
{
    Console.WriteLine( key.ToString() );
}

MoonSharp 库似乎需要并严重依赖 Script class,这是所有其他方法运行的前提。 DoString 方法需要 return 结果,否则 DynValue 将始终是 void/null 。 DynValue 似乎是整个 Lua 进程的基本全局处理程序,它可以处理方法(也就是 lua 字符串可以包含 DynValue 将公开并允许在其中调用的几个方法C# return 将响应作为其他 DynValue 的)

因此,如果您希望加载 lua 文件,该文件仅包含 Lua 的 table 格式的日期,您必须附加 return 和 table 名字作为最后一行。这就是您看到的原因:

"\nreturn DataStore_ContainersDB;"

... 因为 table 名称被称为 "DataStore_ContainersDB"

接下来,必须将结果加载到一个新的 Table 对象中,因为 DynValue 不是一个实际的 table 而是一个 class 结构来保存所有可用的格式(方法, tables, 等).

在它成为 Table 格式后,您现在可以通过键名、数字或 DynValue 调用 key/value 对来使用它。在我的例子中,因为我知道原始键名,所以我直接调用 Table 那里存在我不知道但想使用的键名。

Table.Get( Key )

因为这个 return 是一个 DynValue,所以我们必须 convert/load 对象作为 table 再次使用 .ToObject<> 方法很方便。

我提供的 foreach 循环然后循环遍历位于以下位置的子 table 中可用的键:global > Characters > *

... 然后我使用 key.ToString()

将密钥名称写到控制台

如果还有其他子tables,在这个例子中(因为有),你可以在foreach循环中使用相同的概念通​​过扩展它来遍历未知的这 :

foreach (var key in global.Keys)
{
    if(IsTable(global.Get(key.String)))
    {
        Console.WriteLine("-------" + key.ToPrintString() + "-------");
        Table characterData = global.Get(key.String).ToObject<Table>();
        foreach (var characterDataField in characterData.Keys)
        {
            if( !IsTable(characterData.Get(characterDataField.String)))
            {
                Console.WriteLine(string.Format("{0} = {1}", characterDataField.ToPrintString(), characterData.Get(characterDataField.String).ToPrintString()));
            }
            else
            {
                Console.WriteLine(string.Format("{0} = {1}", characterDataField.ToPrintString(), "Table[]"));
            }
        }
        Console.WriteLine("");
    }
}

... 这是我编写的用于快速检查数据是否为 ​​table 的方法。这是上面foreach例子中使用的IsTable()方法。

private static bool IsTable(DynValue table)
{
    switch (table.Type)
    {
        case DataType.Table:
            return true;

        case DataType.Boolean:
        case DataType.ClrFunction:
        case DataType.Function:
        case DataType.Nil:
        case DataType.Number:
        case DataType.String:
        case DataType.TailCallRequest:
        case DataType.Thread:
        case DataType.Tuple:
        case DataType.UserData:
        case DataType.Void:
        case DataType.YieldRequest:
            break;
    }
    return false;
}

我已尽我所能使它可行,但是,如前所述,我确实看到了改进此递归的空间。检查每个子对象的数据类型,然后加载它感觉非常多余,似乎可以简化。

我对这个问题的其他解决方案持开放态度,理想情况下是以某种增强的形式使它使用起来不那么笨拙。

用于处理表中表,这是我首选的处理方式。我想到了这个。

Script s = new Script();
s.DoString(luaCode);
Table tableData = s.Globals[rootTableIndex] as Table;

for (int i = 1; i < tableData.Length + 1; i++) {
    Table subTable = tableData.Get(i).Table;

    //Do cool stuff here with the data
}

当然这需要您知道全局 rootTable 的索引。

为了我的使用,我做了以下事情(仍在测试)

    string luaCode = File.ReadAllText(Path.Combine(weaponDataPath, "rifles.Lua"));

    Script script = new Script();        
    script.DoString(luaCode);
    Gun rifle = new Gun();
    Table rifleData = script.Globals["rifles"] as Table;

    for (int i = 1; i < rifleData.Length + 1; i++) {

        Table rifleTable = rifleData.Get(i).Table;

        rifle.Name = rifleTable.Get("Name").String;
        rifle.BaseDamage = (int)rifleTable.Get("BaseDamage").Number;
        rifle.RoundsPerMinute = (int)rifleTable.Get("RoundsPerMinute").Number;
        rifle.MaxAmmoCapacity = (int)rifleTable.Get("MaxAmmoCapacity").Number;
        rifle.Caliber = rifleTable.Get("Caliber").String;
        rifle.WeaponType = "RIFLE";

        RiflePrototypes.Add(rifle.Name, rifle);
    }  

这需要对表和值的命名方式做出一些假设,但如果您将其用于对象成员分配,我不明白您为什么会关心 table 中不存在的元素您使用赋值定义的对象的一部分 type.Member = table.Get(member equivalent index).member type