MoonSharp 文档五

目录

13.Coroutines(协程)

Lua中的协程

从CLR代码中的协程

从CLR代码中的协程作为CLR迭代器

注意事项

抢占式协程

[14.Hardwire descriptors(硬编码描述符)](#14.Hardwire descriptors(硬编码描述符))

为什么需要"硬编码"

什么是"硬编码"

如何进行硬编码

硬编码的优缺点

硬编码是实现IL2CPP、AOT或iOS兼容性的必要条件吗?

术语表

15.Sandboxing(沙盒)

为什么需要沙盒化

沙盒化检查清单

[移除"危险"的 API](#移除“危险”的 API)

[16.Tips and tricks for Unity3D(Unity3D的提示和技巧)](#16.Tips and tricks for Unity3D(Unity3D的提示和技巧))

支持的平台

其他建议

使用更显式的构造函数之一初始化脚本加载器

[17.FAQ / Recipes](#17.FAQ / Recipes)

如何重定向打印函数的输出?

如何将输入重定向到Lua程序?

如何重定向Lua程序的IO流?

如何限制脚本在不丢失状态的情况下执行的指令数?


MoonSharp 文档一-CSDN博客

MoonSharp 文档二-CSDN博客

MoonSharp 文档三-CSDN博客

MoonSharp 文档四-CSDN博客

13.Coroutines(协程)

来自 C# 和 Lua。

文档地址:MoonSharp

Lua中的协程

Lua 中的协程是开箱即用的支持。实际上,只要你不故意排除协程模块(参见沙盒化),它们就可以免费使用。有很多注意事项(这些也偶然适用于原始的 Lua 实现),并在下面的 "注意事项" 部分进行了讨论。

使用任何 Lua 协程教程来操作它们。

从CLR代码中的协程

协程可以通过脚本 CreateCoroutine 方法创建,该方法接受一个必须是函数的 DynValue。

cs 复制代码
string code = @"
	return function()
		local x = 0
		while true do
			x = x + 1
			coroutine.yield(x)
		end
	end
	";

// Load the code and get the returned function
Script script = new Script();
DynValue function = script.DoString(code);

// Create the coroutine in C#
DynValue coroutine = script.CreateCoroutine(function);

// Resume the coroutine forever and ever..
while (true)
{
	DynValue x = coroutine.Coroutine.Resume();
	Console.WriteLine("{0}", x);
}

从CLR代码中的协程作为CLR迭代器

可以像调用迭代器一样调用协程:

cs 复制代码
string code = @"
	return function()
		local x = 0
		while true do
			x = x + 1
			coroutine.yield(x)
			if (x > 5) then
				return 7
			end
		end
	end
	";

// Load the code and get the returned function
Script script = new Script();
DynValue function = script.DoString(code);

// Create the coroutine in C#
DynValue coroutine = script.CreateCoroutine(function);

// Loop the coroutine 
string ret = "";

foreach (DynValue x in coroutine.Coroutine.AsTypedEnumerable())
{
	ret = ret + x.ToString();
}

Assert.AreEqual("1234567", ret);

注意事项

现在我们来到了协程相关内容中最重要的部分。就像在原始的 Lua 中一样,无法从嵌套调用中执行 yield 操作。

特别是在 MoonSharp 中,如果你在从 Lua 调用的 C# 函数中调用一个脚本,你不能使用 yield 来恢复到 C# 调用外部的协程。

不过有一个解决办法:返回一个 TailCallRequest 类型的 DynValue

cs 复制代码
return DynValue.NewTailCallReq(luafunction, arg1, arg2...); 

还可以指定一个 continuation(延续)------这是一段函数,它将在尾调用执行完成后被调用。

在 99% 的情况下,这可能是过度设计------甚至在大多数情况下,Lua 标准库也无法正确处理回调与 yield 的结合。但如果你计划自己实现像 `load`、`pcall` 或 `coroutine.resume` 这样的 API,这就是必需的。

另外,在某些边缘情况下,MoonSharp 处理 yield 的方式与标准 Lua 不同(在我尝试过的所有情况中,MoonSharp 的方式都更好,但谁知道呢)。例如,`tostring()` 支持在调用 `__tostring` 元方法时执行 yield 操作,而不会引发错误。

抢占式协程

在 MoonSharp 中,即使协程没有调用 `coroutine.yield`,也可以将其挂起。例如,如果想要非破坏性地限制脚本占用的 CPU 时间,这可能会很有用。但为了保持脚本的一致性,有一些需要注意的地方。

让我们从一个例子开始:

cs 复制代码
string code = @"
	function fib(n)
		if (n == 0 or n == 1) then
			return 1;
		else
			return fib(n - 1) + fib(n - 2);
		end
	end
	";

// Load the code and get the returned function
Script script = new Script(CoreModules.None);
script.DoString(code);

// get the function
DynValue function = script.Globals.Get("fib");

// Create the coroutine in C#
DynValue coroutine = script.CreateCoroutine(function);

// Set the automatic yield counter every 10 instructions. 
// 10 is likely too small! Use a much bigger value in your code to avoid interrupting too often!
coroutine.Coroutine.AutoYieldCounter = 10;

int cycles = 0;
DynValue result = null;

// Cycle until we get that the coroutine has returned something useful and not an automatic yield..
for (result = coroutine.Coroutine.Resume(8); 
	result.Type == DataType.YieldRequest;
	result = coroutine.Coroutine.Resume()) 
{
	cycles += 1;
}

// Check the values of the operation
Assert.AreEqual(DataType.Number, result.Type);
Assert.AreEqual(34, result.Number);

步骤如下:

  1. 创建一个协程

  2. 将 `AutoYieldCounter` 设置为一个大于 0 的数字。1000 是一个不错的起点,可以根据需要调整。这个数字表示在执行多少条指令后,协程会主动挂起并返回给调用者。

  3. 调用 `coroutine.Coroutine.Resume(...)` 并传入适当的参数。

  4. 如果上述调用的返回类型是 `DataType.YieldRequest`,则不带参数调用 `coroutine.Coroutine.Resume()`。

  5. 重复上一步,直到返回一个真正的结果类型。

  6. 完成。

注意事项:

  1. 如果代码重新进入(例如,`string.gsub` 的回调函数),在返回到"可挂起状态"之前,它不会被抢占。

  2. 将标准协程操作与抢占式操作混合使用是可能的,但危险且复杂。请记住,协程可以在任何地方被抢占式挂起,因此无法保证操作的原子性等。如果混合使用,请务必小心。

14.Hardwire descriptors(硬编码描述符

自2016年以来帮助 AOT 和 IL2CPP

文档地址:MoonSharp

如果您不知道本页中使用的某些术语,请参阅底部的术语表部分。

为什么需要"硬编码"

MoonSharp 像大多数类库一样,使用反射,有时还会在运行时生成代码,以便从 Lua 脚本中访问 C# 代码。

从根本上说,MoonSharp 已经比大多数其他脚本引擎更加温和,原因在于:

理论上,它可以在完全不使用反射的情况下运行(尽管需要编写大量代码),这有助于提高速度并避免一些麻烦(如 IL2CPP 的 link.xml 问题)。

它不会在不应该生成IL代码的平台上生成IL代码(如 Xamarin 的 iOS、Unity 的 IL2CPP 平台等)。

前一点不会导致功能损失,只是速度会稍微慢一些。

这些都是优点,并且带来了很大的兼容性,但还可以做得更多。

如果大部分反射都被移除(剩下的少量反射也无害),并且还能获得更好的性能,那会怎样?

听起来好得不像真的?从某种意义上说,是的。这就是"硬编码"的用武之地,但需要付出一些代价。

什么是"硬编码"

这个想法很简单。为了避免反射,目前唯一的方法是编写大量的 C# 胶水代码。非常多。而且是难以编写的 C# 代码。但这并不是必须的------我们可以稍微简化一下编写方式。但这仍然是一项枯燥的工作,而且很容易出错。

然而,事实证明,这种枯燥、困难且容易出错的工作正是自动化的理想场景。而 MoonSharp 拥有完成这项工作所需的所有信息。

所以,简而言之,通过"硬编码",MoonSharp 可以生成一堆不需要进一步反射类型就能运行的 C# 代码。

如何进行硬编码

很简单。嗯,几乎很简单。

MoonSharp 需要将已注册的所有类型的信息导出到某个地方。为此,需要编写一小段临时代码:

cs 复制代码
Table dump = UserData.GetDescriptionOfRegisteredTypes(true);
File.WriteAllText(@"c:\temp\testdump.lua", dump.Serialize());

重要的是,上述代码必须在所有类型都已注册之后运行。因此,硬编码的第一条规则是确保所有注册的顺序正确。

现在,可以使用 MoonSharp 的 REPL 解释器生成代码:

bash 复制代码
moonsharp -W c:\temp\testdump.lua c:\temp\out.cs --internals --class:MyClass --namespace:MyNamespace

此时,生成过程可能会输出大量警告/错误。它们会显示在控制台输出中,并作为代码中的注释。即使存在警告/错误,生成的代码仍然可以工作,但请花时间阅读这些警告/错误,因为如果你的脚本代码引用了触发这些警告/错误的成员,行为可能会与预期不同。

如果你没有 Windows 电脑,你可以轻松地使用 Mono 在 Mac 或 Linux 上运行 MoonSharp 解释器!

生成的代码将包含一个单一的公共静态类,其中有一个公共的静态 `Initialize` 方法,你应该在使用 MoonSharp 进行任何其他操作之前调用这个方法。非常简单。

如果你注册了一个系统类型(例如,数组),在硬编码时可能会遇到一些问题。原因是硬编码将使用生成 "dump" 时运行时的类型定义,如果你更改了 .NET 版本或平台,运行时可能会有所不同。虽然这看起来像是一个边缘情况,但在 Unity 的 Windows Phone 和 Windows Store 应用中会发生这种情况。在最坏的情况下,可以在生成的代码周围加上条件编译指令。

硬编码的优缺点

优点:

• 大幅减少反射的使用

• 在元数据被剥离的情况下(如IL2CPP),无需白名单类型

• 无需运行时代码生成

• 显著提升性能,尤其是在不支持运行时代码生成的平台上

缺点:

• 需要在工作流程中加入代码生成步骤

• 代码生成仅支持 C# 或 VB.NET

• 暴露的类型和成员必须对其他类可访问------简而言之,必须是`public`的,或者在同一程序集中为`internal`

• 某些类型的成员可能不受支持(例如事件,至少在当前版本中)

• 值类型的设置器可能无法正常工作------或者比平时更糟,尽量避免使用它们

• 硬编码系统目前涵盖了所有通过 `UserDataType` 注册的类型,而不传递用户数据描述符。除了自定义描述符(显然需要你自己处理)之外,这意味着与 `PropertyTableAssigner` 和扩展方法一起使用的类型不包括在内。通常这问题不大,因为它们往往用于 IL2CPP 启发式算法可见的类型,未来可能会实现支持。

硬编码是实现IL2CPP、AOT或iOS兼容性的必要条件吗?

AOT 兼容性通过 `PlatformAccessor` 管理。实际上,唯一的行为变化是在 AOT 平台上不执行运行时代码生成,而是使用纯反射。

对于 IL2CPP 兼容性,硬编码简化了这一过程------通过消除为不应剥离的元数据维护 `link.xml` 文件的需求------并使其更高效,因为反射速度较慢。但你仍然可以通过向 `link.xml` 添加条目来实现兼容性。

术语表

反射(Reflection)

一组允许代码"检查"自身并最终调用方法、读取和写入字段及属性等的类型和方法。虽然功能强大,但速度较慢。详见 MSDN 文档。

运行时代码生成(Runtime code generation)

在运行时生成代码以优化通过反射完成的操作的做法。MoonSharp 在某些地方使用了这一技术,但在 AOT 平台上会被禁用。

IL(Intermediate Language,中间语言)

.NET/mono 程序集中的代码以 IL(中间语言字节码)形式存储。这种字节码不能直接执行,必须在执行前转换为本地指令。

JIT(Just-In-Time,即时编译)

通常,当 .NET 或 mono 加载程序集时,它们会对IL代码进行即时编译,将其转换为本地代码。这也可能在程序集的生命周期后期发生------例如,如果泛型类型的某个类型参数是值类型,通常会即时重新编译。

AOT(Ahead-of-Time,预先编译)

mono 提供的一种选项(某种程度上 ngen 和 .NET Native 也提供,但与此讨论无关,至少目前如此),可以提前将代码编译为本地代码或其他形式。这并不简单,可能无法编译所有需要的代码。例如,如果使用反射来实例化代码中从未引用的类型,则该特定代码可能未被编译。在 iOS 设备上运行需要 AOT 执行。

IL2CPP

一种将 IL 转换为 C++ 源代码的出色软件。它是那种极难编写但大多数人谈论它时只是为了抱怨的软件。说真的,它试图解决的问题非常复杂,需要一些合作来确保兼容性。特别是,除了所有 AOT 问题(IL2CPP 必须使用 AOT)外,IL2CPP 还会对程序集元数据进行选择性剥离,这可能会干扰反射。在 Unity3D 下,IL2CPP 是 iOS、tvOS 和 WebGL 的强制要求,并在更多平台上作为选项提供。

link.xml

一个告诉 IL2CPP 应保留哪些类型的元数据的文件。

值类型(Value-type)

值类型是按值传递而不是按引用传递的类型。它包括数值基元类型、枚举和结构体。当谈论值类型问题时,通常指的是结构体。详见 MSDN 上的结构体文档。最佳实践建议值类型应为不可变的,即它们的字段和属性应为只读的,如果需要更改它们,应创建一个新对象(对于结构体来说,这非常廉价)。MoonSharp 与可变结构体的兼容性不佳,因为它们是一种与许多事物都不兼容的奇怪存在,因此请不要使用它们。如果必须使用,请考虑使用代理对象进行变通。

15.Sandboxing(沙盒)

限制 Lua 脚本的功能

文档地址:MoonSharp

为什么需要沙盒化

沙盒化很可能是你使用 MoonSharp 时的关键功能。除非你以某种方式控制脚本提供者(可能还包括用户),否则在运行时加载脚本(或任何类型的代码,有时甚至是数据)时存在一个基本的信任问题:安全性。

例如,假设你正在编写一款视频游戏,并使用 Lua 使游戏"可修改"。用户可以访问某个社区网站并下载包含新关卡、AI等的模组,这些模组都使用 MoonSharp 编写以实现最大灵活性。大家都很开心。现在想象一下,如果某个恶意用户上传了一些隐藏在漂亮关卡中的恶意脚本,会发生什么......比如一个在桌面上创建 .exe 文件的脚本,其他用户可能会不小心点击它。

除非你信任用户,或者明显可以防止恶意使用,否则你不想向普通用户公开某些 API(如`os`、`io`和`file`)。

沙盒化检查清单

这份清单可以帮助你入门,但并不全面。

  1. 确保脚本无法访问所有"危险"的标准 API。这肯定包括`io`、`os`和`file`,但根据你的脚本和偏执程度,可能还包括更多。请参阅下一章,了解如何轻松地使整个API集不可访问。

  2. 不要使用 `InteropRegistrationPolicy.Automatic`。永远不要。

  3. 检查你是否向脚本暴露了不应暴露的类型或类型成员。避免直接暴露你不拥有的类型,如 .NET 框架类型或 Unity 类型。如果需要,请参阅关于"代理对象"的部分,以及如何使用 `MoonSharpHide` 和 `MoonSharpHidden`。

  4. 如果你希望脚本的某些部分访问这些危险的 API,请将它们放在单独的 `Script` 对象中,并确保其他脚本无法访问这些对象。

  5. 如果绝对必须仅对脚本的一部分进行"沙盒化",请参考这份经典指南。

移除"危险"的 API

移除危险 API 的最简单方法是使用接受 `CoreModules` 枚举的 `Script` 构造函数。

`CoreModules` 枚举的含义非常直观:

cs 复制代码
/// <summary>
/// Enumeration (combinable as flags) of all the standard library modules
/// </summary>
[Flags]
public enum CoreModules
{
	/// <summary>
	/// Value used to specify no modules to be loaded (equals 0).
	/// </summary>
	None = 0,

	/// <summary>
	/// The basic methods. Includes "assert", "collectgarbage", "error", "print", "select", "type", "tonumber" and "tostring".
	/// </summary>
	Basic = 0x40,
	/// <summary>
	/// The global constants: "_G", "_VERSION" and "_MOONSHARP".
	/// </summary>
	GlobalConsts = 0x1,
	/// <summary>
	/// The table iterators: "next", "ipairs" and "pairs".
	/// </summary>
	TableIterators = 0x2,
	/// <summary>
	/// The metatable methods : "setmetatable", "getmetatable", "rawset", "rawget", "rawequal" and "rawlen".
	/// </summary>
	Metatables = 0x4,
	/// <summary>
	/// The string package
	/// </summary>
	String = 0x8,
	/// <summary>
	/// The load methods: "load", "loadsafe", "loadfile", "loadfilesafe", "dofile" and "require"
	/// </summary>
	LoadMethods = 0x10,
	/// <summary>
	/// The table package 
	/// </summary>
	Table = 0x20,
	/// <summary>
	/// The error handling methods: "pcall" and "xpcall"
	/// </summary>
	ErrorHandling = 0x80,
	/// <summary>
	/// The math package
	/// </summary>
	Math = 0x100,
	/// <summary>
	/// The coroutine package
	/// </summary>
	Coroutine = 0x200,
	/// <summary>
	/// The bit32 package
	/// </summary>
	Bit32 = 0x400,
	/// <summary>
	/// The time methods of the "os" package: "clock", "difftime", "date" and "time"
	/// </summary>
	OS_Time = 0x800,
	/// <summary>
	/// The methods of "os" package excluding those listed for OS_Time. These are not supported under Unity.
	/// </summary>
	OS_System = 0x1000,
	/// <summary>
	/// The methods of "io" and "file" packages. These are not supported under Unity.
	/// </summary>
	IO = 0x2000,
	/// <summary>
	/// The "debug" package (it has limited support)
	/// </summary>
	Debug = 0x4000,
	/// <summary>
	/// The "dynamic" package (introduced by MoonSharp).
	/// </summary>
	Dynamic = 0x8000,


	/// <summary>
	/// A sort of "hard" sandbox preset, including string, math, table, bit32 packages, constants and table iterators.
	/// </summary>
	Preset_HardSandbox = GlobalConsts | TableIterators | String | Table | Basic | Math | Bit32,
	/// <summary>
	/// A softer sandbox preset, adding metatables support, error handling, coroutine, time functions and dynamic evaluations.
	/// </summary>
	Preset_SoftSandbox = Preset_HardSandbox | Metatables | ErrorHandling | Coroutine | OS_Time | Dynamic,
	/// <summary>
	/// The default preset. Includes everything except "debug" as now.
	/// Beware that using this preset allows scripts unlimited access to the system.
	/// </summary>
	Preset_Default = Preset_SoftSandbox | LoadMethods | OS_System | IO,
	/// <summary>
	/// The complete package.
	/// Beware that using this preset allows scripts unlimited access to the system.
	/// </summary>
	Preset_Complete = Preset_Default | Debug,

}

16.Tips and tricks for Unity3D(Unity3D的提示和技巧)

为在 Unity 上使用 MoonSharp 的用户提供的快速提示集

文档地址:MoonSharp

支持的平台

一般来说,MoonSharp 的目标是支持所有平台。然而,在 Unity 中测试 MoonSharp 在所有平台上的表现是非常困难的,几乎是不可能的。

因此,平台被分为不同的等级。

1.预计完全支持独立的 Windows、Linux 和 OS/X * 安卓 * iOS * tvOS

2.预计零星支持,将在可能的情况下测试小问题/不支持的功能 WebGL * Windows 应用商店应用程序 Windows Phone

3.应该有效,但我很难提供支持其他层没有的东西

4.不支持,您只能自己使用目前尚不清楚,将进行更新以防

其他建议

• 如果可能,不要暴露 Unity 类型

• 尽可能使用代理对象

• 在运行于 IL2CPP 和/或 AOT 平台之前,使用硬编码

• 如果可能,永远不要暴露结构体

使用更显式的构造函数之一初始化脚本加载器

使用 UnityAssetsScriptLoader 的显式构造函数来注册所有脚本文件。通过在项目的最开始使用这个小片段,您不需要将 Unity 自带的库添加到 link.xml 中,就可以在 IL2CPP 平台上运行。

示例:

cs 复制代码
Dictionary<string, string> scripts = new  Dictionary<string, string>();

 object[] result = Resources.LoadAll("Lua", typeof(TextAsset));

 foreach(TextAsset ta in result.OfType<TextAsset>())
 {
     scripts.Add(ta.name, ta.text);
 }

 Script.DefaultOptions.ScriptLoader = new MoonSharp.Interpreter.Loaders.UnityAssetsScriptLoader(scripts);

17.FAQ / Recipes

常见问题的简单示例

文档地址:MoonSharp

如何重定向打印函数的输出?

cs 复制代码
script.Options.DebugPrint = s => { Console.WriteLine(s); }

如何将输入重定向到Lua程序?

cs 复制代码
script.Options.DebugInput = () => { return Console.ReadLine(); }

如何重定向Lua程序的IO流?

cs 复制代码
IoModule.SetDefaultFile(script, Platforms.StandardFileType.StdIn, myInputStream);
IoModule.SetDefaultFile(script, Platforms.StandardFileType.StdOut, myOutputStream);
IoModule.SetDefaultFile(script, Platforms.StandardFileType.StdErr, myErrorStream);

如何限制脚本在不丢失状态的情况下执行的指令数?

参考 Preemptive coroutines.

end

相关推荐
yngsqq17 分钟前
贪心算法和遗传算法优劣对比——c#
算法·贪心算法·c#
鲤籽鲲31 分钟前
C# 事件使用详解
开发语言·c#·c# 知识捡漏
埃菲尔铁塔_CV算法3 小时前
C# WPF 基础知识学习(四)
学习·c#·wpf
xcLeigh5 小时前
WPF与其他技术的集成:与 WinForms、WCF 等协同工作
c#·wpf·优化
秋月的私语7 小时前
在 C# 中,is null 和 == null ‌不完全等价‌
java·前端·c#
东方佑9 小时前
Python中将Markdown文件转换为Word
python·c#·word
咩咩觉主10 小时前
Unity 实现一个可拓展的简单物品交互系统
microsoft·unity·c#·交互
59678515411 小时前
C# ListView设置标题头背景颜色和字体颜色
开发语言·c#
埃菲尔铁塔_CV算法12 小时前
C# WPF 基础知识学习(三)
人工智能·神经网络·学习·计算机视觉·c#·wpf