Unity中的Lua

7.7k 词
文章目錄
	</div>
	
	<p>结合以前的初步学习了解,这一次要对Lua进行进一步的深入学习。</p>

首先我们要知道Lua是脚本语言,是有自己的虚拟机,是解析执行而非像C#,C++,Java这些编译执行。同时Lua是弱类型语言。

这里不更多的讲解Lua虚拟机相关概念,详情参考http://blog.sina.com.cn/s/blog_8c7d49f20102uzsx.html
Virtual_Machine_Working_Levels
这里只要知道Lua的虚拟机(程序模拟线程,堆栈等)是运行在程序里而非物理CPU上,因此只要虚拟机把Lua脚本解析成对应平台的机器码就能支持跨平台(好比Java的JVM)。

Lua Study

以前的部分学习参考:
Scripting System & Lua Study
Lua — some important concept and knowledge
Lua - C API
Lua - C call Lua & Lua call C
Lua - Object Oriented Programming
Lua - Userdata
Lua - Managing Resources & Threads and States

Weak Tables And Finalizers

“and Finalizers Lua does automatic memory management. Programs create objects (tables, threads, etc.), but there is no function to delete objects. Lua automatically deletes objects that become garbage, using garbage collection. “

Weak Table
“Weak tables allow the collection of Lua objects that are still accessible to the program”

“A weak reference is a reference to an object hat is not considered by the garbage collector”

“In weak tables,, both keys and values can be weak”(three kinds of week table:1. weak key 2. weak value 3. weak key and value)

Weak Table Using:

  1. 释放使用不再使用的缓存数据(memorizing)
    Auxiliary table(辅助表),Lua里有类似于C#里的internal hash table用于重用使用过的string和访问结果(C#里主要是使用过的string重用。Lua还能将访问过的string的table结果缓存返回)

但Auxiliary table有个缺点就是使用不频繁的string和result会一直被缓存无法释放。weak table正是用于解决这一问题的方案之一。(因为weak table的weak reference一旦不再被使用就会被下一次GC释放)

  1. Object Attributes Implemetation
    Solution: use external table to associate objects with attributes
    Drawback: external table reference prevent objects from being collected
    Final solution: use weak keys for objects in external table

  2. Tables with Default Values
    这里有两种各有优缺点的实现方案:
    方案1:

    1
    2
    3
    4
    5
    6
    7
    local defaults = {}
    setmetatable(defaults, {__mode = "k"})
    local mt = {__index = function (t) return defaults[t] end}
    function (t, d)
    defaults[t] = d
    setmetatable(t, mt)
    end

方案2:

1
2
3
4
5
6
7
8
9
10
local metas = {}
setmetatable(metas, {__mode = "v"})
function (t, d)
local mt = metas[d]
if mt == nil then
mt = {__index = function () return d end}
metas[d] = mt
end
setmetatable(t, mt)
end

前者针对每一个不同的Table都会分配一个defaults入口,table作为key,default value作为value。这样的缺点就是当table数量很大的时候会需要分配很多内存去存储不同table的default value。

后者针对不同的default value分配了更多的内存(比如mt,entry on metas, closure……),但优点是同样的default value只需要分配一次即可,所以后者更适用于table数量多但default value大都相同的情况。

  1. Ephemeron Tables(声明短暂的table Lua 5.2里提供)
    Ephemeron Table:
    “In Lua 5.2, a table with weak keys and strong values is an ephemeron table. In an ephemeron table, the accessibility of a key controls the accessibility of its corresponding value.(只当有strong
    reference to key时value才是strong的)”
    e.g. constant-function factory

Note:
“Only objects can be collected from a weak table. Values, such as numbers and booleans, are not collectible”(只有Objects在weak table里能被gc回收,number和booleans这种Value类型不能在weak table里被gc回收)

“strings are collectible, but string is not removed from weak table(unless its associated value is collected)”

Finalizers
“Finalizers allow the collection of externa objects that are not directly under control of the garbage collector”

“Finalizer is a function associated with an object that is called when that object is about to be collected.”(相当于C#里的Finalize,在GC object的时候会被调用。但只有一开始设置Metamethod的gc时才能mark该object为可finalization的,否则就算后面在复制gc也不会在调用该object的finalizer)

Lua里是通过Metamethod里的__gc实现。

1
2
3
4
5
-- 测试环境要求至少Lua 5.2
o = {x = "hi"}
setmetatable(o, {__gc = function (o) print(o.x) end})
o = nil
collectgarbage() --> hi

The order of finalization called:
“When the collector finalizes several obejcts in the same cycle, it calls their finalizers in the reverse order that the objects were marked for finalization”(先声明__gc被mark为可finalization的object的finalizer后被调用)

1
2
3
4
5
6
7
8
9
10
11
-- 测试环境要求至少Lua 5.2
mt = {__gc = function (o) print(o[1]) end}
list = nil
for i = 1, 3 do
list = setmetatable({i, link = list}, mt)
end
list = nil
collectgarbage()
--> 1
--> 2
--> 3

对于被finalize的object,Finlization会使被finalize的object处于两个特殊的状态:

  1. transient resurrection(短暂的复苏)
  2. permanent resurrection(永久的复苏)
    前者因为在调用__gc的metamethod时我们会得到finalize的object,这样一来使得该object alive again,然后我们可以通过该object用于访问里面的对象。
    finalizer被调用时,该alive again的object被存储在了global table里,导致该object在finalize后依然存在。要想使用finalize也真正回收obejct,我们需要调用两次gc,第一次用于回收原始object,第二次用于回收alive again的object。
    1
    -- 因为当前测试环境是Lua 5.1(基于LuaTo#插件学习的,JIT支持到5.1的版本)所以不支持Talbe的__gc Metamethod,这里暂时没有写测试程序。

Note:
“In lua 5.1 the only lua values that work with gc metamethod is userdata. “(Lua 5.1不支持table的gc metamethod,只支持userdata的__gc metamethod)

Lua里的OOP

OOP(Object Oriented Programming)
首先Lua里面编写更重要的是Think in Lua,所以这里我不是强调OOP的重要性,而是单纯因为OOP被更多的C#程序员所熟悉。

Class抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
-- File Name:    Class.lua
-- Description: 模拟Lua里OOP特性(不支持多重继承)
-- Author: TangHuan
-- Create Date: 2018/11/09

-- 使用方法:
-- 1. 定义类
-- local ClassName = Class("ClassName", nil)
-- ***(自定义类数据方法等)
-- 参数说明:
-- clsname类名,super父类
-- 返回结果:
-- ClassName(含Class基本信息结构的table)

-- 2. 实例化类对象
-- 参数说明:
-- BaseClass含类定义的Class Table
-- ...构造类实例对象的构造函数ctor()的参数
-- 返回结果:
-- 基于BaseClass的实例对象
-- new(BaseClass, ...)

---克隆对象(建议用于克隆Class对象)
---@param any 对象
---@return any 克隆对象
function Clone(object)
local lookup_table = {}
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for key, value in pairs(object) do
new_table[_copy(key)] = _copy(value)
end
return setmetatable(new_table, getmetatable(object))
end
return _copy(object)
end

---判定是否是指定类或者继承至该类
---@param self Class@类实例对象或者类
---@param cname string | Class@类名或者类定义table
local function IsClass(self, cname)
if type(self) ~= "table" then
return false;
end

if type(cname) == "table" then
if self.class == cname then
return true;
elseif self.super then
return self.super.IsClass(self.super, cname);
end
elseif type(cname) == "string" then
if self.class.className == cname then
return true;
elseif self.super then
return self.super.IsClass(self.super, cname);
end
end
return false;
end

--- 提供Lua的OOP实现,快速定义一个Class(不支持多重继承)
--- 模拟Class封装,继承,多态,类型信息,构造函数等
--- 模拟一个基础的Class需要的信息
---@param clsname string@类名
---@param super super@父类
---@return Class@含Class所需基本信息的Class table
function Class(clsname, super)
local classtable = {}
-- ctor模拟构造函数
classtable.Ctor = false
-- className模拟类型信息,负责存储类名
classtable.className = clsname
-- super模拟父类
-- Note: 外部逻辑层不允许直接._super访问父类
classtable._super = super
-- 自身class类table
classtable.class = classtable;
-- 指定索引元方法__index为自身,模拟类访问
-- 后面实例化对象时会将classtable作为元表,从而实现访问类封装的数据
classtable.__index = classtable
-- 是否是指定类或者继承至某类的方法接口
classtable.IsClass = IsClass;
-- 如果指定了父类,通过设置Class的元表为父类模拟继承
if super then
setmetatable(classtable, super)
else
--print("如果定义的不是最基类,请确认是否require了父类!")
end
return classtable
end

--- 提供实例化对象的方法接口
--- 模拟构造函数的递归调用,从最上层父类构造开始调用
---@param cls cls@类定义
---@param ... ...@构造函数变长参数
---@return cls@cls类的实例对象table
function New(cls, ...)
-- 实例对象表
local instance = {}
-- 设置实例对象元表为cls类模拟类的封装访问
setmetatable(instance, cls)
-- create模拟面向对象里构造函数的递归调用(从父类开始构造)
local create
create = function(cls, ...)
if cls._super then
create(cls._super, ...)
end
if cls.Ctor then
cls.Ctor(instance, ...)
end
end
create(cls, ...)
return instance
end

---静态类
function StaticClass(clsname)
return {}
end

上面的代码注释已经很清楚了,就不一一解释了。

理解上面Lua实现OOP的关键在于通过table模拟Class的抽象以及数据封装,通过metatable模拟继承特性。

Class定义

定义一个类的方式如下:

1
2
3
4
5
6
7
8
9
10
11
类名 = Class("类名")

--构造函数
function 类名:Ctor()
--成员变量定义
end

--方法定义
function 类名:方法名()

end

Class继承

继承一个类的方式如下:

1
2
3
4
5
6
7
8
9
10
11
子类名 = Class("子类名", 父类名)

--构造函数
function 子类名:Ctor()
--成员变量定义
end

--方法定义
function 子类名:方法名()

end

Class对象实例化

Lua OOP对象实例化方式如下:

1
2
3
4
local 变量名 = New(类名)
变量名.成员变量
变量名:成员方法()
变量名.静态方法()

Lua小知识

  1. Lua里没有Class只有通过Table模拟的Class
    参考前面的Lua OOP实现

  2. Lua里没有this的概念,self不等价于this,函数调用传的是谁谁就是self。Lua里.定义和:定义相对于self的用法来说相当于静态和成员定义。
    Lua里通过.调用的话是不会默认传递自身作为self的,不主动传的话self为空
    Lua里通过:调用的话默认第一个参数就是调用者,既self

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    tab = {}
    function tab.dotFunc(param)
    print("self = ", self)
    print(param)
    end

    function tab:colonFunc(param)
    print("self = ", self)
    print(param)
    end

    tab.dotFunc(1)
    tab:colonFunc(1)

    LuaDotAndColon
    Note:
    :只是起了省略第一个参数self的作用,该self指向调用者本身,并没有其他特殊的地方。

  3. Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)(在Lua中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值)

    1
    2
    3
    4
    5
    6
    7
    8
    
      
        
        

    2.4lua控制结构

    how quickly do stock market valuations revert back to their means?