1. Lua はどのようにホット アップデートを実行しますか?
Lua はコンパイルする必要がないため、Lua コードは Lua 仮想マシンで直接実行できます。C# コードが実行を開始する前に、メモリのコード セグメントに一緒にインストールされ、新しいコードを更新する方法はありません。
動的読み込み: app + 組み込みのスクリプト インタープリター、このスクリプト インタープリターによってスクリプト コードが動的に実行される
Lua = Lua インタープリター + Lua スクリプト
2. Lua にはどのようなデータ型がありますか
nil (空型)、boolean、number (数値型、細分化されていない)、string、function、userdata、thread、table
3. Lua の文字列
Lua 文字列は、短い文字列と長い文字列に分けられます。
短い文字列: 長さが 40 以下の短い文字列。同じ文字の短い文字列は同じものを共有します。
長い文字列: 長さが 40 を超える文字列は、長い文字列です。同じ 2 つの長いストリングは、2 つの別個のストリングです。
4. Lua のテーブルの内部データ構造
Lua テーブルには、実際にはすべてのデータをまとめて格納するための配列 (配列) とハッシュ テーブルが含まれています。その中で、配列はキー値が 1 ~ n のデータを格納するために使用され、1 ~ n は連続している必要があります。他のデータはハッシュテーブルに保存されます。なお、Luaの配列はキーが連続する整数(1~n)でなければならないので、途中で穴が開いてしまうと後続のデータがハッシュテーブルに格納されてしまう可能性があります。
さらに、デジタルのキーと値のペアの数が多すぎると、ハッシュ テーブルにも格納されます (つまり、配列の容量が不足します)。
5. Lua でテーブルの競合を解決する方法
オープン アドレッシング
6. Lua での GC
Mark and Sweep GC アルゴリズムは、各 GC ですべてのオブジェクトをスキャンし、オブジェクトへの参照がない場合はリサイクルされます。Lua 5.1 以降では、3 色インクリメンタル マーク スイープ アルゴリズムが使用されます。利点: GC ですべてのオブジェクトを一度にスキャンする必要がなくなりました. この GC プロセスはインクリメンタルで、中断、再開、継続が可能です. 3 色は次のように分類されます。
白: オブジェクトが GC によってマークされていないことを示します。これは、作成後のオブジェクトの初期状態でもあります。つまり、GC スキャン プロセスの後もオブジェクトがまだ白い場合、そのオブジェクトはシステム内のどのオブジェクトからも参照されておらず、そのスペースを再利用できることを意味します。(白はさらに白1と白2に分けられます)。
灰色: 現在のオブジェクトは待機状態にあり、オブジェクトが GC によってアクセスされたことを示しますが、オブジェクトによって参照されている他のオブジェクトにはアクセスされていません。
黒: 現在のオブジェクトがスキャンされ、そのオブジェクトが GC によってアクセスされ、そのオブジェクトによって参照されている他のオブジェクトにもアクセスされたことを示します。
備考:白は白1と白2に分かれています。理由: GC マーキング フェーズが終了し、クリーンアップ フェーズがまだ開始されていない場合、新しいオブジェクトが作成された場合、参照関係が見つからないため、原則として白としてマークされるべきであり、その後のクリーンアップ フェーズが続きます。白がクリアされるというルール オブジェクトのクリア、これは不合理です。上記の状況が発生した場合でも、lua は新しく作成されたオブジェクトを白としてマークしますが、それは「現在の白」(白 1 など) です。ただし、lua はクリーニング段階で「古い白」(白 2 など) のみをクリーニングし、クリーニングが終了すると「現在の白」を更新します。つまり、白 2 が現在の白になります。
7. ペアと ipair の違いは何ですか?
ペアはテーブル内のすべての要素をトラバースし、ipairs は前の連続した要素のみをトラバースします
--下面lua代码运行的结果是
local a = {
[1]=2,[2]=3,[4]=a,[5]=c }
for i,v in ipairs(a) do
print(v)
end
结果
2
3
ipairs顺序遍历数组的部分,遍历到3找不到就自动退出了
8. テーブルの長さを取得するには?
テーブル コンテンツが連続している場合は # を使用できます。また、キーと値の形式がある場合は、カウントをトラバースする必要があります。
9. テーブルの並べ替え
table.sort(needSortTable, func) を使用して、値に従って並べ替えます。必要に応じて func を書き換えることができます。それ以外の場合は、デフォルトに基づきます。デフォルトのケースでは、文字列型と数値型が存在する場合table の場合、2 つの型を直接比較するためエラーになるため、func を記述して自分で変換する必要があります。
local test_table = {
2,1,3,"SORT","sort"}
table.sort(test_table , function(a , b)
return tostring(a) > tostring(b)
end)
10. 文字列を連結するには?
table.concat または ...
"..." を使用すると、連結されるたびに新しい文字列が生成され、lua で新しい文字列が生成されるたびに、その文字列をグローバル ステート テーブルの strt フィールドに格納する必要があります。 (global_State), 連結の数が増えるにつれて、新しい文字列を格納するためにより多くのスペースが必要になります. 一定のサイズに達すると、古い文字列を GC する必要があります. 新しいスペースと GC を継続的に開くと、パフォーマンスが低下します.
table.concat は、テーブル内の要素を文字列に変換し、それらを区切り記号で接続します。table.concat は頻繁にメモリに適用されるわけではなく、8192 BUFF がいっぱいになったときにのみ TString が生成され、最終的に複数の TString が生成されると、メモリの適用とマージが行われます。大規模な文字列をマージする場合は、この方法を選択するようにしてください。
11. テーブルが nil か空のテーブルかを判断する方法
--next查找下一个值,默认从第一个值开始
function IsTableEmpty(t)
return t == nil or next(t) == nil
end
12. __index、__newindex、__call、__tostring メソッドの使用法?
local mytable = {
1, 2, 3}
local mymetatable = {
__index = {
b = 6},
__newindex = function(table, key)
print(__newindex ..key)
end
,
__call = function(self, newtable)
sum = #newtable
return sum
end
,
__tostring = function(self)
return #self
end
}
--设置元表
mytable = setmetatable(mytable, mymetatable)
--获取对象元表
getmetatable(mytable)
--元表设置了__index方法,mytable找不到b元素(也可以是方法)就会去元表的__index中找
print(mytable.b)
--表里添加新元素时会调用__newindex方法
mytable.c = 3
--当表作为函数形式被调用时,进入__call方法
newtable = {
1,2,3}
print(mytable(newtable))
--直接调用mytable,进入__tostring方法
print(mytable)
13. Lua で読み取り専用テーブルをセットアップする方法
--在元表的__newindex中抛出异常
function table_read_only(t)
local temp= t or {
}
local mt =
{
__index = function(t,k)
return temp[k]
end,
__newindex = function(t, k, v)
error("attempt to update a read-only table!")
end
}
setmetatable(temp, mt)
return temp
end
--用法:
local ta = {
1,2,3}
local tb = table_read_only(ta) --tb为只读
tb[5] = 1 --对表进行更新,会报错:attempt to update a read-only table!
14. __rawset と __rawget の違いは何ですか?
テーブルを検索するとき、値がテーブルに存在しない場合、テーブルのメタテーブルを検索し、メタテーブルの __index フィールドにアクセスして解決します。そして、テーブルに新しい値が入力されると、テーブルのメタ テーブルを検索し、そのメタ テーブルの __newindex フィールドにアクセスして解決します。そして、rawset & rawget は、メタテーブルの処理をバイパスして、このテーブルを操作した結果を直接出力します。
rawset(テーブル、キー、値)、テーブルにキーのペアを追加します。値のキーは
文字列形式である必要があります。他の形式を入力すると、エラーが報告され、メタテーブルの __newindex はトリガーされません。 get table[index ] にrawget
(table, index)を追加して
も、メタテーブルの __index はトリガーされません
15. Lua と C# の最下層と対話する方法
C# と Lua の相互作用は主に仮想スタックを通じて実現されます. スタックのインデックスは正と負の数に分けられます. インデックスが正の場合は 1 がスタックの底を意味し, インデックスが負の場合は -1 を意味します.スタックの一番上を意味します。
C# Call Lua: C# は要求またはデータをスタックの一番上に置き、lua はスタックの一番上からデータを取得し、対応する処理 (クエリ、変更) を lua で実行し、処理結果をスタックの一番上に戻します。スタック、最後にスタックから C# lua で処理されたデータを取り出し、インタラクティブな
Lua Call C# を完成させる: (1) Wrap メソッド: まず C# ソース ファイルに対応する Wrap ファイルを生成し、Lua から Wrap ファイルを呼び出します。ファイルを作成し、Wrap ファイルから C# ファイルを呼び出します。
(2) リフレクション メソッド: システム API、dll ライブラリ、またはサード パーティ ライブラリをインデックス化するときに、コードの特定の実装をコード生成できない場合、このメソッドを使用して相互作用を実現できます。短所:実行効率が低い。
16. Lua を実行する 3 つの方法
1.执行字符串
void Start()
{
//lua环境对象,一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一。
LuaEnv luaenv = new LuaEnv();
//注意这里hello world使用单引号,这样符合C#规则
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
//调用lua的print方法,前面会有LUA: 标识
luaenv.DoString("print('hello world2')");
luaenv.Dispose();
}
2.加载lua脚本运行
void Start()
{
LuaEnv luaenv = new LuaEnv();
//加载lua脚本,文件放在Resources下,因为Resource只支持有限的后缀,
//放Resources下的lua文件得加上txt后缀,如LuaTestScript.lua.txt
//TextAsset ta = Resources.Load<TextAsset>("LuaTestScript.lua");
//luaenv.DoString(ta.text);
//require加载文件必须放在Resources下,可以不用写.lua后缀
//require实际上是调一个个loader去加载,有一个成功就不再往下,
//全失败则报文件找不到。
luaenv.DoString("require 'LuaTestScript'");
luaenv.Dispose();
}
3.通过自定义loader方式加载文件
void Start()
{
LuaEnv luaenv = new LuaEnv();
//自定义loader加载的lua脚本可以放在任意合法路径,且不需要添加.txt后缀
luaenv.AddLoader(MyLoader);
//调用require时会优先使用自定义的MyLoader加载文件
//如果自定义loader中有返回值,就不会再去执行系统的loader调用
luaenv.DoString("require 'Test'");
luaenv.Dispose();
}
private byte[] MyLoader(ref string fileName)
{
//加载streamingAssets目录下的文件
string absPath = Application.streamingAssetsPath + "/" + fileName + ".lua";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));
}
17. C# で lua を呼び出す
1.获取一个全局基本数据类型
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//获取lua文件中的全局变量
int a = luaenv.Global.Get<int>("a");
Debug.Log(a);
luaenv.Dispose();
}
2.访问一个全局的table
<1>映射到普通class或struct
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//xLua会帮你new一个实例,并把对应的字段赋值过去。
//table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。
//要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。
Person p = luaenv.Global.Get<Person>("person");
print(p.name + "-" + p.age);
//修改class的字段值不会同步到table,反过来也不会。
p.name = "haha";
luaenv.DoString("print(person.name)");
luaenv.Dispose();
}
class Person
{
public string name; //必须是public且名称一致
public int age;
}
/* Lua中的表
person = {
name = "siki",
age = 100,
Eat = function(self, a, b)
print(a + b)
end
}
*/
<2>利用接口做映射,推荐使用
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),
//代码生成器会生成这个interface的实例。如果get一个属性,生成代码会get对应的table字段,
//如果set属性也会设置对应的字段。也可以通过interface的方法访问lua的函数。
IPerson p1 = luaenv.Global.Get<IPerson>("person");
print(p1.name + "-" + p1.age);
//通过接口访问,相当于引用,会修改lua中的值
p1.name = "heihei";
luaenv.DoString("print(person.name)");
p1.Eat(1, 2);
luaenv.Dispose();
}
[CSharpCallLua]
public interface IPerson
{
string name {
get; set; }
int age {
get; set; }
void Eat(int a, int b);
}
<3>通过Dictionary,List做映射
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//Dictionary只能接受表里键值对形式的
Dictionary<string, object> dict = luaenv.Global.Get<Dictionary<string, object>>("person");
foreach (string key in dict.Keys)
{
print(key + "-" + dict[key]);
}
print("-----------");
//List只能接受表里非键值对形式的
List<object> list = luaenv.Global.Get<List<object>>("person");
foreach (object obj in list)
{
print(obj);
}
luaenv.Dispose();
}
<4>通过LuaTable获取,速度慢,不常用
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
LuaTable tab = luaenv.Global.Get<LuaTable>("person");
print(tab.Get<string>("name"));
print(tab.Get<int>("age"));
luaenv.Dispose();
}
3.访问一个全局的function
<1>映射到delegate,性能好,类型安全,推荐使用
[CSharpCallLua]
public delegate int Add(int a, int b, out int sub);
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//要生成代码(否则会抛InvalidCastException异常)
Add add = luaenv.Global.Get<Add>("add");
int sub;
int res = add(12, 24, out sub);
print(res);
print(sub);
//引用不置空,Dispose时会报错
add = null;
luaenv.Dispose();
}
/* Lua中的方法
function add(a, b)
print(a + b)
return a + b, a - b
end
*/
<2>映射到LuaFunction,性能差
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
LuaFunction func = luaenv.Global.Get<LuaFunction>("add");
object[] os = func.Call(12, 24);
foreach (object o in os)
{
print(o);
}
luaenv.Dispose();
}
18. Lua が C# を呼び出す
1.通过CS.访问不同命名空间下构造函数,静态属性,方法
local newGameObj = CS.UnityEngine.GameObject()
--读静态属性
CS.UnityEngine.Time.deltaTime
--写静态属性
CS.UnityEngine.Time.timeScale = 0.5
--调用静态方法
CS.UnityEngine.GameObject.Find('helloworld')
--调用成员方法,建议用冒号语法糖
testobj:DMFunc(100)
--xlua只一定程度上支持重载函数的调用,C#的int,float,double都对应于lua的number,
--上面例子中DMFunc如果有这些重载参数,如DMFunc(int x) DMFunc(float x)将无法区分开来,只能调用生成代码中排前面的
2.将table映射到参数为结构体,类,接口,List,Dictionary的方法
--[[
C#中定义如下结构体,class和接口也支持,接口需要打标签[CSharpCallLua]
public struct A
{
public int a;
}
public struct B
{
public A b;
public double c;
}
成员函数如下:
public void Foo(B b)
--]]
--在lua可以这么调用
obj:Foo({
b = {
a = 100}, c = 200})
3.将function映射到委托,需要打标签
--[[
C#中定义如下委托
[CSharpCallLua]
public delegate void MyDelegate(int num);
成员函数
public void Foo(MyDelegate p)
--]]
myDelegate = function(num)
print("lua中对应的委托方法"..num)
end
obj:foo(myDelegate)
19. Lua はどのようにオブジェクト指向を実装していますか?
Lua のクラスは、テーブルと関数によってシミュレートされ、継承は __index と setmetatable によってシミュレートされ、ポリモーフィズムは親クラスのメソッドを書き換えることによってシミュレートされます。
-- 定义基类
Base = {
name = "base", age = 99 }
function Base:showInfo()
print(self.name .." 年龄 "..self.age)
end
-- 定义基类的new方法,用于构造子类
function Base:new()
local sub = {
}
--写法1
--setmetatable(sub, { __index = self })
--写法2
self.__index = self
setmetatable(sub, self)
return sub
end
-- 创建对象
Sub = Base:new()
Sub:showInfo()
-- 多态
function Sub:showInfo()
print(self.name)
end
Sub:showInfo()
20. ドット呼び出しとコロン呼び出しの違いは何ですか?
person = {
name = "aaa", age = 10}
person.sleep = function()
print("在睡觉")
end
function person:eat()
print(self.name.."在吃饭")
end
--没用到self的方法,冒号调用和点调用都一样
person.sleep()
person:sleep()
--用到self的方法,通过点调用第一个参数为当前的table
--冒号是语法糖,通过冒号调用系统会自动传递当前的table给self
person.eat(person)
person:eat()