MoonSharp 從一知到半解(3)

0x00

MoonSharp 是一個支持使用 C# 調用 Lua 的類庫,這個系列是通過官網的教程來入門類庫與 Lua。

如果有需要請配合我的代碼食用。

0x01 Proxy objects

處於安全性和兼容性的考慮,使用 Proxy Objects,將實現細節封裝,保證 API 不會調用任何不應調用的字段,讓內部實現與 API 獨立,因此可以在不破壞腳本的情況下修改內部的實現。爲了不讓封裝 API 過於複雜,MoonSharp 提供了一種腳本 DTO,也就是 Proxy Objects.

DTO(Data Transfer Object,數據傳輸模型),是一個在進程之間傳遞數據的對象,常用在與遠程接口的通訊上(如 web 服務)(expensive operation)。

這個概念非常簡單。對於要封裝的腳本所暴露的每種類型,必須提供“代理類型”類,它通過所封裝的類型的一個實例類進行相關的操作。

這個示例就是示例,Github 裏可以直接執行。

// Declare a proxy class
class MyProxy
{
    MyType target;

    [MoonSharpHidden]
    public MyProxy(MyType p)
    {
        this.target = p;
    }

    public int GetValue() { return target.GetValue(); }
}

// Register the proxy, using a function which creates a proxy from the target type
UserData.RegisterProxyType<MyProxy, MyType>(r => new MyProxy(r));

// Test with a script - only the proxy type is really exposed to scripts, yet everything it works
// just as if the target type was actually used..

Script S = new Script();

S.Globals["mytarget"] = new MyType(...);
S.Globals["func"] = (Action<MyType>)SomeFunction;

S.DoString(@"
        x = mytarget.GetValue();
        func(mytarget);
");

稍微理解一下,這裏其實是通過 MyProxy 通過一個實例轉調了 MyType 的方法,在腳本中訪問 MyType 實際上是通過 MyProxy 進行的。

除了封裝之外,可以使用“代理對象”完成一些巧妙的技巧。

一個非常簡單,但非常有用的例子是提供對值類型的正確訪問;

例如,你可以包裝 Unity Transform 類——它完全由值類型組成,但本身是一個引用類型——使其具有不同的接口,並且可以正確保留引用!

(哈?)

0x02 Error handling

MoonSharp 從 InterpreterException 類型派生出了四種異常類型:

  • InternalErrorException 解釋器內部錯誤,腳本引擎致命錯誤,聯繫開發者吧?
  • SyntaxErrorException 解釋器無法解析腳本代碼;
  • ScriptRuntimeException 最常見的錯誤,引發 Lua 錯誤時會拋出,git 中有個例子,可以在 c# 中捕獲異常,或向 lua 拋出異常(pcall 進行處理)。
  • DynamicExpressionException 解釋器遇到動態表達式(Dynamic expression,假裝瞭解的樣子……)錯誤時拋出。

當然,也可能由於調用代碼或 MoonSharp 代碼導致其他的異常,但至少理論上不會由腳本產生。上面的異常類型還有一個包含錯誤信息的 DecorativeMessage 屬性,它使用拋出異常的源碼的引用進行修飾。

0x03 Script Loaders

如何修改 MoonSharp 讀取文件中腳本的方式。MoonSharp 旨在支持多平臺,所以要適配用戶選擇的平臺。因此 MoonSharp 提供了兩個對象層級:

  • Script loaders 用來自定義 API 如何從文件加載腳本,(emmmm,可以簡單理解爲用在 Script 類中咯);
  • Platform accessors 用來自定義如何完成對 OS 的低級訪問。

預定義腳本加載器

根據使用的平臺不同可以選擇的腳本加載器:

  • FileSystemScriptLoader:對文件系統上文件的原始訪問,可以自定義,但是不支持可移植庫;
  • ReplInterpreterScriptLoader:與 FileSystemScriptLoader 相同,加上使用與 Lua 相同的邏輯來獲取環境變量的路徑(MOONSHARP_PATH,LUA_PATH 或 “?;?.lua”);
  • EmbeddedResourcesScriptLoader:提供對給定程序集的嵌入資源的訪問,而非文件系統;
  • InvalidScriptLoader:拋出異常;
  • UnityAssetsScriptLoader:適用於 Unity3D,從文本資源加載腳本。

如果不重定義,MoonSharp 會使用的默認腳本加載器:

在 Unity 下,默認腳本加載器是 Assets/Resources/MoonSharp/Scripts 路徑的 UnityAssetsScriptLoader,而文件名必須具有“.txt”擴展名,奇怪的 Unity 爲什麼只認識 txt。

如果構建可移植類庫,選擇 InvalidScriptLoader(除非執行某些操作,否則無法從文件進行加載)。

其他情況下,使用 FileSystemScriptLoade。

指定加載器

如果希望從當前程序集的嵌入資源加載,可以有兩種方式(全局或局部):

// 給定一個 script 實例,指定此實例的加載器
script.Options.ScriptLoader = new EmbeddedResourcesScriptLoader();
// 後續創建的所有新 script 的加載器
Script.DefaultOptions.ScriptLoader = new EmbeddedResourcesScriptLoader();

require 方法

通常的需求是改變 require 方法使用的加載目錄。腳本加載器擴展自 ScriptLoaderBase 類,通過修改 ModulePaths 屬性即可。

// These two lines are equivalent:
((ScriptLoaderBase)script.Options.ScriptLoader).ModulePaths = new string[] { "MyPath/?", "MyPath/?.lua" };
// 或者
((ScriptLoaderBase)script.Options.ScriptLoader).ModulePaths = ScriptLoaderBase.UnpackStringPaths("MyPath/?;MyPath/?.lua");

如果想忽略 LUA_PATH(全局變量),可以這樣:

((ScriptLoaderBase)script.Options.ScriptLoader).IgnoreLuaPathGlobal = true;

使用 EmbeddedResourcesScriptLoader

很簡單,在 VS 裏:

  1. 在工程中創建一個“Scripts”文件夾;
  2. 添加文本文件重命名爲“test.lua”;
  3. 寫 Lua…

將文件添加到工程(添加文件夾直接拖拽到工程中就好,我不知道你會不會,反正我剛會……),然後將開開“.lua”的屬性,修改“生成操作(Build Action)”爲“嵌入的資源(Embedded Resource)”如圖。

在這裏插入圖片描述

然後,就是最喜歡的寫兩行代碼環節:

static void EmbeddedResourceScriptLoader()
{
    Script script = new Script();
    script.Options.ScriptLoader = new EmbeddedResourcesScriptLoader();
    script.DoFile("Scripts/Test.lua");
}

那麼,這種嵌入資源方式和直接讀取文件有啥區別呢?

創建自己的腳本加載器

  • 繼承 ScriptLoaderBase(官方推薦)
  • 實現 IScriptLoader

不言自明~

0x04 Platform accessors

平臺訪問器與腳本加載器非常相似(是不是可以結束這一 part 了)。

平臺訪問器提供了對操作系統 API 的訪問,主要是 io,file 和 os 模塊。

  • StandardPlatformAccessor:實現所有相關方法;
  • LimitedPlatformAccessor:禁用操作系統模塊(構建可移植類庫時默認使用)。

要注意的是,修改了平臺訪問器會影響所有腳本。一旦創建了腳本,就不應該更改平臺訪問器了。

類似地,要實現自己的平臺訪問器可以繼承 PlatformAccessorBase 或實現 IPlatformAccessor。通常一些功能(打印輸出重定向)都是可以通過腳本選項定製的。

0x05 Script options

Script 對象提供了選項來調整其行爲,有全局和局部兩種選項,放到附錄請查閱。

本地選項可以爲每一個 Script 對象實例賦不同的值。而全局選項通過 Script.GlobalOptions 訪問,對所有腳本有效。



0xff

附錄I

局部選項 描述
ScriptLoader This is the IScriptLoader for this script. See the section on script loaders for more details.
DebugPrint This is a Action delegate which will be called everytime print is called by Lua code.
DebugInput This is a Func<string, string> delegate which will be called everytime MoonSharp requires line-input from a console, for example in the case of debug.debug.
UseLuaErrorLocations If this is set to true, locations in error messages will only include the line numbers instead of lines and columns. Use this for compatibility with legacy Lua code which parses error messages.
Stdin, Stdout and Stderr Default streams to be used in io/file functions.
TailCallOptimizationThreshold This is quite a complex option. Basically Lua uses a technique to drammatically reduce stack usage in certain scenarios at the expense of simplicity and ease of debugging. MoonSharp can behave the same way, but by default doesn’t until it’s needed. This defines the threshold. Refer to the reference help for more details.
CheckThreadAccess Again a complex and unusual option. MoonSharp does some sanity checks to prevent scripts being used by multiple threads concurrently. For reasons detailed in the reference help, these checks can have false positives and false negatives. If you are experiencing false positives, you can disable the checks with this option. But please check that you are not calling MoonSharp execution concurrently from two threads as it is not supported.
全局選項 描述
Platform the platform accessor we learned about in the previous chapter
CustomConverters the collection of all custom converters
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章