使用lua已經1年多了, 至今還常常驚嘆于其作者設計的簡潔和提供給用戶的一套機制, "工具就在那里擺著, 去發(fā)揮你的想象力吧"~~~ lua的接口最好的體現(xiàn)了提供機制而不是策略的思想. 在游戲編程精粹中, 看到一篇關于介紹腳本語言的文章, 其中不光介紹了lua, 也介紹了GameMonkey :) 大概做了一下了解, 發(fā)現(xiàn)國內好像尚無使用的先例, 資料也比較少, 本著學習借鑒, 開拓思路的態(tài)度, 決定翻譯GameMonkey的官方資料, 希望能對需要的人有幫助. 其中也有我自己認為要詳細說一下的, 提醒一下的, 用rotc注出來了
comments 注釋
// 和c++一樣注釋到本行末
/*
和c / c++ 一樣的注釋塊
*/
注釋塊會被編譯器忽略掉, 它的作用是給代碼做注釋或者臨時使一段代碼失效[調試腳本時常用]
變量和常量
GameMonkey不像c, Pascal那樣的強類型語言, 它更像是Basic語言.
GM中的變量是以下類型中的一個:
null -- 沒有值, 這有類型
int -- 32bit的有符號整數
float -- 32bit的浮點數
string -- null結尾的ansi字符串
table -- 數組/hash容器
function -- 函數
user -- 用戶自定義類型
rotc: 如何理解null 是一種類型, 而不是一個值?
對c++er 和 cer來說, null/NULL基本就是0, 或(void*)0, 它的確是一個具體的值. 這里說GM中的null是個類型, 可能會在理解上有一定的困難. 根源是靜態(tài)類型和動態(tài)類型造成的. 靜態(tài)類型語言中, 類型和值是分裂開來的, 對于靜態(tài)類型語言中的變量i來說, 如果能夠通過編譯, 那么i的類型就肯定是確定的. 靜態(tài)類型語言中的類型, 只是用來說明如何對&i這樣一個內存地址做解釋(但問題在于這個說明是在編譯期就必須決定的). 也就是說c中的變量直接映射到了內存和如何解釋內存. 而動態(tài)語言通過引入一個間接層, 其值的結構是 Value(type, realvalue), 也就是說, 一個變量由一個內存和附帶一個指示如何解釋該內存的標志(及類型)組成的. 這樣的好處是顯而易見的, 可以在運行時改變變量類型(也就是改變對內存的解釋方式), 下面演示動態(tài)語言中如何實現(xiàn)變量賦值時決定類型.
比如我創(chuàng)造了一門動態(tài)語言, 這門語言中有2個類型, 那么這樣實現(xiàn)
enum {null, man, woman}; // 兩個類型加一個null類型
struct Value{ Value(){type = null; pData = 0;} char type; void* pData}; // 動態(tài)語言中的變量
struct Man {Man(int h, int c){housevalue = h; carvalue = c;} int housevalue; int carvalue}; // 男類型內容是房產和車產
struct Woman { Woman(char* name) {strcpy(sweetname, name);} char sweetname[12]; }; // 女類型有一個可愛的名字
在我的腳本中:
Value pp; // 定義個一個變量, 注意, 現(xiàn)在這個變量不是一個Man, 也不是一個Woman, [但它有類型--null, 但是它沒有值]
pp = Man(5,3); //制造一個富家男, 注意pp 現(xiàn)在的類型由null變成man, 值是一個Man
// 實現(xiàn) void operator = (Value& v, Man& m) {
v.type = man; // 賦類型
v.pData = &m; // 賦值
}
pp = Woman(“X姐"); // 制造了一個X姐[芙蓉姐, 鳳姐], 注意pp現(xiàn)在的類型由man變成women了, 值是一個Woman
// 實現(xiàn) void operator = (Value& v, Man& m) {
v.type = woman; // 賦類型
v.pData = &m; // 賦值
}
pp = null;
// 實現(xiàn) ..... v.type = null;
當你掩去c++的實現(xiàn)時, 腳本:
Value pp;
pp = Man(5, 3);
pp = Woman(“X姐”);
pp = null;
上面就展示了如何在腳本語言中實現(xiàn)所謂的一個變量既能存int (Man), 又能存string(Woman), 還能只有類型沒有值(type==null)的奧秘, 其實就是引入了一個間接層, 把靜態(tài)語言編譯時就要確定某個內存地址要怎么解釋, 變成了{解釋方式, 內存}這種形式, 這樣的話, 可以在運行期改變解釋方式和值[當然他們是匹配的], [ 可以注意到, 動態(tài)分配內存是支持這種實現(xiàn)的不可缺少的機制, 垃圾收集的需求也伴隨而來]
最后總結: null表示不解釋~~~:) 你懂的
GM中, 變量名是大小寫敏感的, __t0 和 __t1保留做內部使用.
變量名 = [a..zA..Z] + [a..zA..Z_0..9]*
例子:
a = null; // a 沒有值
b = 4; // b 是int類型
c = 4.4; // c 是float類型
d = “hello”; // d 是string類型
e = table(); // e是一個空的表
f = function() {}; // f 是一個函數
更多的例子:
a = ‘SMID’; // a 是一個int, 值為(‘S’<<24 | ‘M’<<16 | ‘I’<<8 | ‘D’)
b = .23; // b 是一個float
c = 2.4f; // c 是一個float
d = ‘c:\windows\sys’; // d是一個string
語言和它的標準函數總是試圖保留值, 然而不是保留類型. 具體規(guī)則是當不同類型的變量在一起運算時, 高級別的類型將被保留. 類型從低級到高級的順序是: int, float, string.
例子:
print(“hello” + 4); // 輸出: hello 4, 4的類型被提高
print(2 + 5); // 輸出: 7, int類型被保留
print(2.0 + 5); // 輸出: 7.0, 5的類型被提高
print(sqrt(17.0)); // 輸出: 4.1231, float類型被保留
print(sqrt(17)); // 輸出: 4, int類型被保留
int類型賦值的例子:
a = 179; // 十進制
a = 0xB3; // 十六進制
a = 0b0011001 // 二進制
a = ‘BLCK’; // 字符轉成4個byte分別賦予int的四個byte中
float類型賦值例子:
b = 45.34; // float十進制
b = .345; // float
b = 289.0; // float
b = 12.34f; // c風格float
b = 2.3E-3; // 科學計數法
字符串賦值例子:
c = “c:\\path\\file.ext”; // 標準雙引, 用\做轉義字符
c = ‘c:\path\file.ext’; // 和上面一樣, 單引情況下, \不做轉義字符用
c = “Mary says \”hello\””; // 相當于'Mary says "hello"'
c = 'Chris' 's bike'; // 相當于'Chris's bike', 也就是說在單引內部表示單引的方法是連續(xù)兩個單引
c = “My ” “house”; // 相當于"My house"
基礎類型可以使用標準內建庫進行顯示的轉換, Int(), Float(), String()
例子:
a = 10;
b = a.String(); // 這樣是最好的, 顯示的調用類型轉化函數, 返回轉化后的值
b = “” + a; // 這樣不好, 賦值會將a的類型提升到string, 但是效率底下
b = (10).String(); // 丑陋的
b = 10.String(); // 錯誤的, 編譯不過, 因為編譯器不認同這種語法
引用類型變量的可引用類型有String, Function, Table, User. 當這些變量被賦值時, 并不發(fā)生full copy, 而只是讓變量指向具體的obj
例子:
a = table(“apple”, "orange"); // a是一個指向table的引用
b = a; // b 現(xiàn)在和a指向同一個table
b[1] = "banana"; // 設置b[1]
print(a[0], a[1]); // >> banana orange
print(b[0], b[1]); // >> banana orange
當一個變量被賦新值時, 該變量原來持有的值就有可能丟失掉了.
例子:
Oper = function(a, b){
return a + b
}; // Oper現(xiàn)在指向一個函數
Oper = “hello”; // Oper現(xiàn)在指向字符串, 原來的函數被丟失了
函數
語法: function(<params>) { <statements> };
一個函數體是一個值, 而函數是一個類型 {type = GM_FUNCTION, value=function...}
注意: 記住在將函數賦值給變量后面那個分號, 這是語法必須的
例子
// 將一個創(chuàng)建一個rect table的函數賦值給CreateRect
CreateRect = function(posX, posY, sizeX, sizeY){
rect = table(x=posX, y=posY, width=sizeX, height=sizeY);
rect.Area = function() {return .width * height; };
return rect;
};
myRect = CreateRect(0, 0, 5, 10); // 創(chuàng)建一個用于描述rect的table
area = myRect.Area();
// 可以用:代替.來隱式的傳遞一個this指針
Size = function(){
return .width * .height;
};
s = myRect:Size(); // 調用時, myRect會當做this指針傳入Size中
作用域
和作用域有關的一些關鍵字, 語法:
global <variable>
Local <variable>
member <variable>
this
this.<variable>
.<variable>
函數中的變量.
默認情況下, 一個在函數中使用的變量就是這個函數的本地變量. 如果要聲明一個全局變量, 需要使用global關鍵字. 訪問成員變量必須通過this或者是使用member關鍵字聲明它是一個成員變量. 在局部使用的變量可以用local關鍵字聲明.
例子:
Access = function(){ // Access 是一個local變量, 它引用著一個函數
apple = 3; // apple 是函數的一個local變量
global apple; // 把apple聲明成全局作用域
local apple; // 把apple聲明成局部作用域
member apple; // 把apple聲明成 this的member變量
this.apple; // 明確的訪問this.apple
.apple // 隱式的訪問this.apple
};
例子:
a = 13; // a 是一個local作用域變量
print(b); // b是null
global b = function() { // b是一個全局作用域的變量, 類型是GM_FUNCTION
global c = 2; // c是一個全局作用域的變量
d = 3; // d是函數局部作用域變量
{ if (c == 2)
{ local e = 3; } // e 從這一刻開始成為函數局部作用域變量, 注意沒有塊作用域變量
}
print(e); // e = 3
}
在查找一個變量時, 按照local 和 parameters, 然后global的順序查找.
成員變量有微妙的不同:
h = function() { // h 是一個local變量
global a = 3; // a 是一個全局變量
member n; // n可以從this被訪問和創(chuàng)建
d = 3; // d是函數局部作用域
this.b = 3; // b是member作用域
.b = .x + 1; // b, x都是member作用域
print(b); // b 是 null, 因為這里并沒有l(wèi)ocal的b
print(n); // 就像print(this.n)一樣, 因為上面顯示聲明過了
};
全局作用域中的語句.
x = 7; // local
global x = 8; // global
a = function(y) {
local x = 5; // function local
dostring(“print(x);”); // 這里打出8, 和lua一樣, dostring總是在全局環(huán)境下編譯運行的, 無法訪問function的變量和parameters
};
變量可以是虛擬機全局作用域的, 也可以是某個函數作用域的, 或者是某個obj比如table的成員作用域的. 當執(zhí)行一個文件或者是執(zhí)行一個字符串的時候, 和lua一樣, 文件或者是字符串被編譯成一個無名的函數, 所以默認情況下, 其中最外層的未加特別申明的變量是該無名函數的函數作用域的.
this總是存在的. 它或者是null, 或者是一個有效的值. 你可以傳遞this, 或者是使用重載冒號操作符默認的this. 這一特性多用在創(chuàng)建諸如類似模板行為, 那些地方的obj的操作往往只有run-time時才能確認. this也用在創(chuàng)建線程, 例子:
obj:thread(obj.DoThings) // 開始一個線程, 并把obj作為this傳遞給它
obj:MakeFruit(apple, orange) // 調用MakeFruit, 并把obj當做this傳給它
語法和操作符
! Not 邏輯取反
~ 每一個bit位取反
^ bit位使用與或 XOR
| bit位使用或 OR
& bit位使用與 AND
>> bit位右移
<< bit位左移
~= bit位取反賦值
^= bit位 XOR 賦值
|= bit位 OR 賦值
&= bit位 AND 賦值
>>= bit位 右移 賦值
<<= bit位 左移 賦值
= 賦值
' 單引 其中的字符會當做int值
" 雙引 字符串(處理轉義字符)
` 反引 字符串(不處理轉義字符)
[] 方闊 用index取talbe元素
. 取table元素
: 給函數傳遞this
+ 數學+
- 數學-
* 數學*
/ 數學/
% 模取余
+=, –=, *=, /=, %= 數學運算并賦值
{} 界定語句塊
; 語句結束標志
<, <=, >, >=, == 數學比較
&&或and 邏輯AND
|| 或or 邏輯OR
Tables 表
語法: table(<key> = <value>, ...);
table(<value>, …);
{<key>=<value>, …, };
{<value>, …, };
table可以被同時認為是array 和 map. 因為table中可以容納data和function, 所以table也可以被認為是class, table中也可以容納別的table, 這時它也已被認為是Tree.
初始化table的例子:
fruit = table("apple", "banana", favorite= "tomato", "cherry");
fruit = {"apple", "banana", favorite="tomato", "cherry"};
這時, fruit的樣子就是:
fruit[0] = “apple”;
fruit[1] = “banana”;
fruit[2] = “cherry”;
fruit[“favorite”] = "tomato"; 也可以寫作是 fruit.favorite = "tomato"
可以注意到, fruit.favorite="tomato"并沒有占據 element[2], 雖然它在邏輯上應該是element[2]的位置, 但是它不是一個index索引成員, 是一個{key, value}成員.
從表中取得元素的例子.
a = thing.other; // other 是table thing中的一個成員
b = thing[“other”]; // 相當于b = thing.other
c = thing[2]; // c取得了thing中的第三個indexd索引成員
index = 3;
d = thing[index]; // 用int做下標, 就可以把table當數組訪問
accoc = “fav”;
e = thing[accoc]; // 用string做下標, 就可以把table當map訪問
注意, thing["Fav"]和thing["fav"]是兩個不同的東西, 因為GM是大小寫敏感的. 這樣做設計上的考慮是:
1) 賦值可能是string, 也可能是任何類型的值.
2) 要做到大小寫無關, 底層需要一些額外的工作量, 這會產生一定量的效率問題.
設置table中成員的值的例子.
thing.other = 4;
thing[3] = “hello”;
嵌套表的例子:
matrix = { {1, 2, 3,}, {4, 5, 6}, {7, 8, 9,}, } //
print(“matrix[2][1] = ”, matrix[2][1]); // 輸出"matrix[2][1] = 8"
關鍵字if和else
語法: if ( <condition> ) { <statements> }
或者 if ( <condition> ) { <statements> } else { <statements> }
或者 if ( <condition> ) { <statements> } else if ( <condition> ) { <statements> } else { <statements> }
例子:
foo = 3;
bar = 5;
if ((foo * 2) > bar){
print(foo * 2, “is greater than”, bar);
}
else{
print(foo * 2, “is less than”, bar);
}
// 輸出: 6 is greater then 5
if 會計算條件表達式的值, 并根據其結果的true/false來選擇執(zhí)行那一段語句.
if 在計算條件時, 會像大多數語言那樣, 并且實現(xiàn)了短路求值, 下面是一些例子:
if (3 * 4 + 2 > 13) == if ( ( (3*4) + 2) > 13 )
if (3 > 0 || 0 > 1) 3 > 0恒真, 那么永遠不會去對0 > 1求值
對c程序員的提示: 你不能把condition和一個單語句無語句塊標示的statements寫在同一行, 這是語法不允許的
例: if ( a > 3) b = 4; // 錯誤, b = 4 必須被{}包起來
關鍵字for
語法: for (<statement1>; <condition>; <statement2>) { <statements> }
例子:
for (index = 0; index < 6; index = index + 2){
print(“Index = ”, index);
}
輸出是:
Index = 0
Index = 2
Index = 4
for 語句的執(zhí)行和大多數語言一樣, 循序是
1. 執(zhí)行 statement1
2. 執(zhí)行condition, 是false就退出for
3. 執(zhí)行statements
4. 執(zhí)行statement2, goto 2
關鍵字foreach
語法: foreach (<key> and <value> in <table>) { <statements> }
foreach (<value> in <table>) { <statements> }
例子:
fruitnveg = table("apple", "orange", favorite = "pear", yucky="turnip", "pinapple");
foreach(keyVar and valVar in fruitnveg){
print(keyVar, “=", valVar);
}
輸出是:
2 = pinapple
0 = apple
favorite = pear
1 = orange
yucky = turnip
注意到遍歷tale的時候, 它并沒有按料想的順序來輸出. 事實上, 這種順序會在table中的元素填入和刪除時發(fā)生變化.
在foreach的每次迭代過程中, key和value將會作為循環(huán)體的local作用域變量. 在迭代過程中, 對table執(zhí)行刪除元素操作是安全的, 但是向table中新增元素和從table刪除元素是[原文是: Although the foreach iteration is ‘delete safe’, the behaviour of adding and removing items from a table while iterating is undefined. 我理解不了delete safe 和 removing items from a table] 請大家告訴我好的理解, 我好改正
關鍵字 while
語法: while( <condition> ) { <statements> }
例子:
index = 0;
while ( index < 3 ) {
print("index = ", index);
index = index + 1;
}
輸出:
index = 0
index = 1
index = 2
while結構先檢查條件, 如果條件為真就執(zhí)行循環(huán)體并反復執(zhí)行這一過程直到第一次檢查到條件為假. 如果一開始條件就為假, 那么循環(huán)體中的代碼一次也不會執(zhí)行.
關鍵字 dowhile
語法: dowhile (<condition>) { <statements> }
例子:
index = 0;
dowhile (index > 0) {
print("index = ", index);
}
輸出:
index = 0
dowhile和while不同, 它先執(zhí)行循環(huán)體, 然后檢測條件已決定是否要再次執(zhí)行循環(huán)體, 循環(huán)體中的代碼至少執(zhí)行一次.
關鍵字 break, continue, return
break的例子:
for (index = 0; index < 4; index = index + 1) {
if (index == 2) {
break;
}
print(“index =”, index);
}
輸出:
index = 0
index = 1
continue的例子:
for (index = 0; index < 4; index = index + 1) {
if (index == 2) {
coutinue;
}
print(“index = ”, index);
}
輸出:
index = 0
index = 1
index = 3
return 的例子:
Add = function(a, b) {
return a + b;
}
print(“Add res = ”, Add(4, 5));
輸出:
Add res = 9
Early = function(a) {
if (a < = 0) {
print(“Dont want zero here.”);
return ;
}
print(“Above zero we handle.”);
}
Early(-2);
輸出:
Dont want zero here.
break和continue用來退出或者是忽略 for, while, dowhile, foreach 循環(huán). break會使執(zhí)行流跳出循環(huán)語句塊, continue導致終止本輪迭代, 并立即進行下一輪迭代, return不光是能跳出循環(huán), 它是直接跳出當前函數.
關鍵字true, false, null
在GM中, true和false分別表示0和1. 除此之外沒有別的意義. 注意, 和其他語言不太一樣的地方
例子:
a = 3;
if (a == true) {
print(a, “==", true);
}else{
print(a, “!=", true);
}
輸出:
3 != 1
null是一種類型, 而不是一個值, 它通常用來表示錯誤. 當它和其他類型混用時, 它的類型會被提升, 值為0. 當一個變量被聲明但沒有賦值時, 這個變量就是一個null. 對于table中的元素, 如果被賦值為null, 就表示這個元素被從table中刪掉了.
例子:
local var;
if (var) { // var聲明了但沒賦值, 所以是null, 這里類型提升了, 值為0 == false
print(“var was initialised or non zero : ”, var);
} else {
print(“var is zero or null : ”, var);
}
輸出:
var is zero or null : null
--------------------------------------------------- 高潮分割線 ---------------------------------------------------------
上面那些我覺得正常人1~2個小時應該能掌握了, 我翻譯的昏昏欲睡了, 下面是一些GM內建的機制, 能夠體現(xiàn)出一些特色, 這個正是我想要的:), 其實可以把下面的這些東西看做是GM的庫, 也可以看成是GM內置的功能.
Thread
rotc: 這里有幾點要說的
1. GM里的thread不是通常的線程, 其實就是lua里的協(xié)程.
2. GameMonkey開發(fā)的初衷有彌補當時的LuaV4沒有協(xié)程的遺憾, 現(xiàn)在luaV5已經有了.
3. 從接口來看, GM中的協(xié)程接口更加豐富易用.
1. int thread(function a_function, …)
創(chuàng)建一個線程.
a_function 是線程的執(zhí)行體
... 是傳給a_function的參數
該函數返回一個thread id, 控制和查詢線程都必須通過這個id來進行.
2. void yield()
調用該函數導致當前執(zhí)行體讓出對GM虛擬機的控制權.
3. void exit()
調用本函數導致當前執(zhí)行體立即退出.
4. void threadKill(int a_threadId)
kill掉指定的線程, 被kill掉的線程不能再次運行.
a_threadId是要kill的線程的id, 由thread()函數返回.
如果調用threadKill()將導致當前線程被kill掉.
5. void threadKillAll(bool a_killCurrentThread = false)
kill掉所有的線程, 參數為false的話kill掉出自己外的所有線程, 否者連自己也kill掉.
6. void sleep(float a_time)
停止當前執(zhí)行體指定的秒數.
7. int threadTime()
返回當前線程執(zhí)行的總時間, 單位是毫秒.
8. int threadId()
返回當前線程的id
9. table threadAllIds()
用一個table返回所有的線程id.
10. void signal(var a_event)
引發(fā)一個事件. a_event是任意類型的, 表示一個事件.
11. void block(var a_event, ...)
讓當前線程阻塞在一個或多個事件上. 只到事件發(fā)生, 該線程才能被轉化為可執(zhí)行的.
States
在游戲編程中, 常使用狀態(tài)的概念來描述一個游戲實體的行為, 就是常常說到的有限狀態(tài)機. 在GM中, states允許一個線程結束后馬上丟棄這個線程的棧并跳到另一個執(zhí)行點開始執(zhí)行.
1. void stateSet(function a_function, …)
設置當前執(zhí)行線程的新的狀態(tài)函數.
a_function 表示要執(zhí)行的狀態(tài)函數.
... 表示要傳給狀態(tài)函數的參數.
2. function stateGet()
獲取當前執(zhí)行的狀態(tài)函數. 如果之前沒有調用過stateSet, 那么將返回null.
3. function stateGetLast()
獲取當前狀態(tài)的前一個狀態(tài), 可以用來得知變遷信息.
4. void stateSetExitFunction(function a_function)
設置一個函數, 該函數將在狀態(tài)變遷時調用. 可以用來在進入下一個狀態(tài)前本次狀態(tài)函數本身的一些清理工作, 如果沒有下一個狀態(tài), 那么這個函數不會被執(zhí)行.
rotc: 其實這個state的實現(xiàn)要求虛擬機實現(xiàn)了尾遞歸, 否者在狀態(tài)過多的時候會導致滿棧, 實現(xiàn)了尾遞歸和協(xié)程的語言都可以做出states類似的功能, 但是GM中顯示的給出的支持, 也是很方便的.
System
1. void debug()
使調試器在這里產生一個斷點.
2. void assert(int a_condition)
檢查a_condition, 它必須是非0值, 否者產生一個異常然后退出線程.
3. int sysTime()
返回虛擬機執(zhí)行的實現(xiàn), 單位是毫秒.
4. int doString(string a_script, int a_executeNow = true)
編譯并執(zhí)行a_script中的腳本
a_executeNow == true的話, script將馬上被執(zhí)行, 然后doString函數才返回, 否者返回新建的thread id.
實質的步驟是:
1. 把 a_script 編譯成一個函數func
2. 調用 id = thread(func)
3. if a_executeNow == true
yield()
else
return id
5. int typeId(variable a_var)
返回a_var的類型值
6. string typeName(variable a_var)
返回a_var的類型名字
7. int typeRegisterOperator(int a_typeid, string a_opName, function a_func)
給指定的類型注冊一個操作
a_typeid 目標類型
a_opName 操作名
a_func 現(xiàn)實的操作函數
返回1成功, 0失敗.
a_opName的取值: getdot, setdot, getind, setind, add, sub, mul, dov, mod, inc, dec, bitor, botxor, shiftleft, shiftright, bitinv, lt, gt, lte, gte, eq, neq, neg, pos, not
8. int typeRegisterVariable(int a_typeid, string a_varName, variable a_var)
給指定的類型注冊一個變量, 使用(type).varname的方式就可以獲得這個變量
a_typeid 目標類型
a_varName 要加的變量名
a_var 要加的變量
返回1成功, 0失敗.
9. int sysCollectGarbage(int a_forceFullCollect = false)
如果當前內存使用量超過了指定的內存使用量, 那么執(zhí)行垃圾回收.
a_forceFullCollect 如果垃圾回收可用的話, a_forceFullCollect=true將馬上開始執(zhí)行
返回1表示垃圾回收執(zhí)行了, 其他情況返回0.
10. int sysGetMemoryUsage()
返回當前的內存使用量, 單位byte.
11. void sysSetDesiredMemoryUsageHard(int a_desired)
設置內存使用量硬限制. 在垃圾回收時會根據這個值來決定要不要執(zhí)行一次完整的回收.
a_desired 內存使用硬限制, 單位是byte.
12. void sysSetDesiredMemoryUsageSoft(int a_desired)
設置內存使用量軟限制. 在垃圾回收時會根據這個值來決定是否開始增量回收. soft值必須小于上面的hard值, 謝謝
a_desired 內存使用軟限制, 單位是byte.
13. void sysSetDesiredMemoryUsageAuto(int a_enable)
開啟或者關閉在接下來的垃圾收集中是否能自動調整內存限制.
a_enable 1 開啟 0 關閉
14. int sysGetDesiredMemoryUsageHard()
獲取內存使用量硬限制, 單位是byte. 注意, 這個值是用在開始一次完整的垃圾回收前的檢測.
15. int sysGetDesiredMemoryUsageSoft()
獲取內存使用量軟限制, 單位是byte. 注意, 這個值是用在開始增量垃圾回收前的檢測.
16. int sysGetStatsGCNumFullCollects()
返回虛擬機執(zhí)行完整垃圾回收的次數.
17. int sysGetStatsGCNumIncCollects()
返回虛擬機執(zhí)行增量垃圾回收的次數. 注意在restart的那一次回收中, 這個值會+2.
18. int sysGetStatsGCNumIncWarnings()
返回GC 或者VM因為配置的問題[soft和hard限制]而導致的警告的數量. 如果這個數龐大而且急速增加, 那么gc的軟硬內存限制應該重新配置以獲得更好的性能表現(xiàn). 這些警告的出現(xiàn)一般意味著gc次數過于平凡或不足. 如果這個值很小, 或者是增長很慢, 那么不用去擔心它. 可以閱讀介紹GM的gc的文檔[翻譯完這個, 我就翻譯GM gc的文檔]來獲取關于gc話題的很多信息. 我們將在以后的版本中改進這個函數, 以便讓它的意義很明確易懂.
表操作
1. int tableCount(table a_table)
計算table中元素的個數.
2. table tableDuplicate(table a_table)
返回傳入table的一個副本.
我測過了, copy的深度就是a_table的元素這一層, 比如
t1={a=9}; t2={uu=t1, b=45};
t3 = tableDuplicate(t2);
t3.b = 78;
t3.uu.a = 80;
print(“t2.b = ”, t2.b); // t2.b = 45
print(“t3.b = ”, t3.b); // t3.b = 78
print(“t2.uu.a = ”, t2.uu.a); // t2.uu.a = 80
print(“t3.uu.a = ”, t3.uu.a); // t3.uu.a = 80
啥內涵大家一看就明白了
------------------------------------------華麗的風格線-------------------------------------------
綁定C函數到GM腳本中
C函數可以綁定到類型上, 也可以綁定到一個全局變量.
一個可以綁定到GM中的C函數的原型是:
int GM_CDECL gmVersion(gmThread* a_thread)
a_thread->GetNumParams() 可以獲得參數的個數
a_thread->Param*() 獲取各個參數
a_thread->GetThis() 訪問this
a_thread->Push*() 向腳本中返回值
還有一些有用的宏和簡寫的功能函數.
C函數的返回值描述如下:
GM_OK 函數執(zhí)行成功
GM_EXCEPTION 函數執(zhí)行失敗, 在函數運行的thread中產生一個運行時異常
當然函數也可能返回一些控制thread行為的值, 比如GM_SYS_SLEEP, GM_SYS_YIELD, GM_SYS_KILL, 這些值可以讓腳本的高級用戶實現(xiàn)和修改虛擬機的行為. 用戶擁有強大的控制權, 可以更高效的實現(xiàn)參數變量, 重載函數(通過支持不同類型的參數), 檢查錯誤或無效的輸入.
一個GM操作符綁定函數的原型是:
void GM_CDECL dunc(gmThread* a_thread, gmVariable* a_operands)
a_operands[0] 是左參數
a_operands[1] 是右參數
a_operands[0] 同時也是返回值
如果操作符函數不能執(zhí)行該操作(比如錯誤的參數類型等), 就把a_operands[0]置為null
對于二元操作符來說, 比如O_MUL, 調用操作符函數時將選擇兩個參數類型較高的參數的綁定函數. NOT是一個一元操作符(這時將使用a_operands[0].m_type的綁定函數). 這一點和c++是不一樣的, 在c++中, 如果你創(chuàng)建了一個類 Vec3, 那么Vec3 * float 的運算就需要重載一個*操作符, 而float * Vec3需要重載一個全局的友元函數. GM這樣做是為了降低原生類型的處理代價和易于用戶定義類型的擴展. 所以原生的int 和 float 類型不需要在意那些比他們高級的類型, 但是用戶自定義類型例如Vec3可以很有彈性的和低級類型一起工作, 它的綁定函數將被調用.
可能發(fā)生沖突的地方就是當用戶自定義類型之間發(fā)生運算時, 如果用戶知道注冊的順序的話, 他們可以依據這個來編碼, 否者可能要實現(xiàn)同樣的操作符函數來保證不會發(fā)生因為注冊順序而導致的問題. 兩個用戶類型可以給一個操作符綁定同樣的操作符函數, 這樣可以避免不必要的重復.
rotc: 上面這兩段話我翻譯的不好, 先放著, 等對這部分知識有了更深的理解再來修改
例子1, 實現(xiàn)一個可以注冊到GM中的C函數, 比較簡單, 不寫注釋了
// int GetHealth(GObj* a_obj, int a_limit)
int _cdecl GetHealth(gmThread* a_thread) {
GM_CHECK_NUM_PARAMS(2);
GM_CHECK_USER_PARAM(GObj::s_scrUserType, userObj, 0);
GM_CHECK_INT_PARAM(limit, 1);
Gobj* gob = (Gobj* )userObj->m_user;
if (gob->m_health > a_limit) {
gob->m_health = a_limit;
}
a_thread->PushInt(gob->m_health);
return GM_OK;
}
例子, 向GM中導入一個函數, 使得在GM中可以使用sqrt(56)或者sqrt(67.8), 過程比較簡單就不寫注釋了
int __cdecl gmfSqrt(gmThread* a_thread) {
GM_CHECK_NUM_PARAMS(1);
if (a_thread->ParamType(0) == GM_INT) {
int intVal = a_thread->Param(0).m_value.m_int;
a_thread->PushInt((int)sqrt(intVal));
return GM_OK;
} else if (a_thread->ParamType(0) == GM_FLOAT) {
float floatVal = a_thread->Param(0).m_value.m_float;
a_thread->PushFloat(sqrtf(floatVal));
return GM_OK;
}
return GM_EXCEPTION;
}
static gmFunctionEntry s_mathLib[] = {
{"sqrt", gmfSqrt}, };
machine->RegisterLibrary(s_mathLib, sizeof(s_mathLib) / sizeof(s_mathLib[0]));
例子, 為String類型加上一個Compare操作的演示, 使得可以在GM中使用 "hihi".Compare("hihi") , 因為比較重要, 給出完整代碼.
#include "gmThread.h"
int GM_CDECL gmfStringCompare(gmThread* a_thread)
{
GM_CHECK_NUM_PARAMS(1);
// Compare的參數必須是string, 因為這個函數預期將進行字符串的比較
if (a_thread->ParamType(0) == GM_STRING)
{
// 獲取調用Compare的變量
const gmVariable* var = a_thread->GetThis();
// 這個變量一定也是一個string
GM_ASSERT(var->m_type == GM_STRING);
// gm str ----> c str
gmStringObject* obj = (gmStringObject* )GM_OBJECT(var->m_value.m_ref);
const char* thisStr = (const char* )*obj;
const char* otherStr = a_thread->ParamString(0);
// 具體的操作
a_thread->PushInt(strcmp(thisStr, otherStr) ? 0 : 1);
return GM_OK;
}
return GM_EXCEPTION;
}
static gmFunctionEntry s_stringlib[] = {
{"Compare", gmfStringCompare},
};
int main(int argc, char* argv[])
{
// 先創(chuàng)建虛擬機
gmMachine machine;
// 注冊到虛擬機
machine.RegisterTypeLibrary(GM_STRING, s_stringlib, 1);
// 好了可以用了:)
machine.ExecuteString("print("res = ", \"hihi\".Compare(\"hihi\"));");
getchar(); // Keypress before exit
return 0;
}
程序執(zhí)行結果是輸出 res = 1
從C中調用GM腳本
從C中調用GM腳本時使用gmCall輔助類會讓整個事情變得很簡單, 下面就是一個例子:
#include “gmCall.h” // 要使用gmCall就必須包含這個頭文件
gmMachine machine; // 初始化一個GM虛擬機
// 我們要調用的函數是: global Add = function(a, b) { return a + b; };
gmCall call;
int resultInt = 0;
if (call.BeginGlobalFunction(&machine, “Add”)) {
call.AddParamInt(3);
call.AddParamInt(5);
call.End();
call.GetReturnedInt(resultInt); // 取結果
}
警告: 如果你從函數中返回一個string, 那么你就馬上使用它, 或者是把它copy出來, 不要長期的持有這個指針. 因為這個字符串不會一直有效, 說不定在下一輪的垃圾收集中就把它回收了, 這樣的話, 你再次使用它的指針時就很危險了.
游戲對象擴展
我怎樣才能擴展GM, 向它中添加一個我自己定義的類型, 就像table那樣子.
怎樣在GM中表達一個game obj?
下面的代碼就是完整的將GameObject類型導入到GM中, 包含創(chuàng)建, 訪問, 內存管理的各個方面
struct GameObject {
gmTableObject* m_table; // 容納表功能
gmUserObject* m_userObject;
static gmType s_typeId; // 存儲自己定義的類型
};
gmType GameObject::s_typeId = GM_NULL;
#if GM_USE_INCGC
static bool GM_CDECL GCTrace(gmMachine* a_machine, gmUserObject* a_object, gmGarbagCollector* a_gc, const int a_workLeftToDo, int& a_workDone) {
GM_ASSERT(a_object->m_userType == GameObject::s_typeId);
GameObject* object = (GameObject* ) a_object->m_user;
if (object->m_table)
a_gc->GetNextObject(object->m_table);
a_workDone += 2;
return true;
}
static void GM_CDECL GCDestruct(gmMachine* a_machine, gmUserObject* a_object) {
GM_ASSERT(a_object->m_userType == GameObject::s_typeId);
GameObject* object = (GameObject* )a_object->m_user;
object->m_table = NULL;
}
#else
// 垃圾回收的標記函數
void GM_CDECL GameObjectMark(gmMachine* a_machine, gmUserObject* a_object, gmuint32 a_mark) {
GM_ASSERT(a_object->m_userType == GameObject::s_typeId);
GameObject* obecjt = (GameObject* )a_object->m_user;
object->m_table->Mark(a_machine, a_mark);
}
// 垃圾回收的回收函數
void GM_CDECL GameObjectGC(gmMachine* a_machine, gmUserObject* a_object, gmuint32 a_mark) {
GM_ASSERT(a_object->m_userType == GameObject::s_typeId);
GameObject* object = (GameObject* )a_object->m_user;
object->m_table.Destruct(a_machine);
delete object;
}
#endif
// 設置一個用來描述類型的字符串以便在調用"AdString"得到它
static void GM_CDECL AsString(gmUserObject* a_object, char* a_buffer, int a_bufferLen) {
GM_ASSERT(a_ojbect->m_userType == GameObject::s_typeId);
GameObject* object = (GameObject* ) a_object->m_user;
char mixBuffer[128];
sprintf(mixBuffer, “GameObject Cptr = %x”, object);
int mixLength = strlen(mixBuffer);
int useLength = GM_MIN(mixLength, a_bufferLen - 1);
GM_ASSERT(useLenght > 0);
strncpy(a_buffer, mixBuffer, useLength);
a_buffer[useLengrh] = 0;
}
// get dot操作符用來訪問table
void GM_CDECL GameObjectGetDot(gmThread* a_thread, gmVariable* a_operands) {
GM_ASSERT(a_operands[0].m_type == GameObject::s_typeId);
gmUserObject* user = (gmUserObject* )GM_OBJECT(a_operands[0].m_value.m_ref);
GameObject* object = (GmaeObject* )user->m_user;
a_operands[0] = object->m_table->Get(a_operands[1]);
}
// set dot操作符用來訪問table
void GM_CDECL GameObjectSetDot(gmThread* a_thread, gmVariable* a_operands) {
GM_ASSERT(a_operands[0].m_type == GameObject::s_typeId);
gmUserObject* user = (gmUserObject* )GM_OBJECT(a_operands[0].m_value.m_ref);
GameObject* object = (GameObject* )user->m_user;
object->m_table->Set(a_thread->GetMachine(), a_operands[2], a_operands[1]);
}
// 從腳本中創(chuàng)建GameObject
// 注意: 游戲中像這樣直接創(chuàng)建對象實體并不常見, 還有, 可能并不像保存對象的c指針, 取而代之的是保持一個32bit的UID來代表對象, 在使用的時候通過UID來查詢和驗證對象
int GM_CDECL CreateGameObject(gmThread* a_thread) {
GameObject* object = new GameObject();
object->m_table = a_thread->GetMachine()->AllocTableObject();
object->m_userObject = a_thread->CreateUser(object, GameObject::s_typeId);
return GM_OK;
}
// 獲取一個object, 這種情況下的obj在c和腳本中是一對一的.
int GM_CDECL GetGameObject(gmThread* a_thread) {
GameObject* foundObj = NULL;
GM_CHECK_NUM_PARAMS(1);
if (a_thread->ParamType(0) == GM_STRING) {
// todo: foundObj = FindByName(a_thread->ParamString(0));
// 如果找到的話
a_thread->PushUser(foundObj->m_userObject);
a_thread->PushNull();
return GM_OK;
}
else if (a_thread->ParamType(0) == GM_INT) {
// todo: foundObj = FindById(a_thread->ParamInt(0));
// 如果找到的話
a_thread->PushUser(foundObj->m_userObject);
a_thread->PushNull();
return GM_OK;
}
return GM_EXCEPTION;
}
// 注冊函數
gmFunctionEntry regFuncList[] = {
{"GameObject", CreateGameObject},
}
// 向虛擬機注冊類型, 假定虛擬機已經構造好了
// 1. 注冊新類型
GameObject::s_typeId = machine.CreateUserType(“GameObject”);
// 2. 注冊垃圾回收要用到的
#if GM_USE_INCGC
a_machine->RegisterUserCallbacks(GameObject::s_typeId, GameObjectTrace, GameObjectDestruct, AsString);
#else
a_machine->RegisterUserCallbacks(GameObject::s_typeId, GameObjectMark, GameObjectGC, AsString);
#endif
// 為類型綁定get dot操作
machine.RegisterTypeOperator(GameObject::s_typeId, O_GETDOT, NULL, GameObjectGetDot);
// 為類型綁定set dot操作
machine.RegisterTypeOperator(GameObject::s_typeId, O_SETDOT, NULL, GameObjectSetDot);
// 注冊函數
machine.RegisterLibrary(regFuncList, sizeof(regFuncList) / sizeof(regFuncList[0]));
虛擬機的回調
如果一個應用程序擁有它自己的gmObject, 它必須讓垃圾回收器知道這個對象正在被使用. 要做到這一點, 你需要在虛擬機回調中捕獲MC_COLLECT_GARBAGE 消息, 這個回調發(fā)生在垃圾回收器開始掃描根時. 一個讓gc正確管理c++持有的gmObject的代換方案是使用gmMachine::AddCPPOwnedGMObject() 和 gmMachine::RemoveCPPOwnedGMObject(). 第三種方法是使用gmGCRoot<>指針來實現(xiàn).你可以通過查閱GM gc的文檔來獲取更多這方面的知識.
應用程序可能希望在thread創(chuàng)建和銷毀時執(zhí)行一些動作, 比如管理這個thread專有的object等. 此外, 應用程序可能希望將thread的異常信息導入到error stream中. 下面是一些列子.
// 假定你在別處已經建立了虛擬機, 現(xiàn)在你要注冊callback函數
gmMachine::s_machineCallback = ScriptCallback_Machine;
//
bool GM_CDECL ScriptCallback_Machine(gmMachine* a_machine, gmMachineCommand a_command, const void*a_context) {
switch (a_command) {
case MC_THREAD_EXCEPTION: {
// dump 異常信息導到標準輸出
bool first = true;
const char* entry;
while ( (entry = a_machine->GetLog().GetEntry(first)) ) {
fprintf(stderr, “%s“, entry);
}
a_machine->GetLog().Reset();
}
break;
case MC_COLLECT_GARBAGE": {
#if GM_USE_INCGC
gmGarbageCollector* gc = a_machine->GetGC();
// 對于所有的c 擁有的obj
// gc->GetNextObject(obj);
#else
gmuint32 mark = *(gmuint32 *)a_context;
// 對于所有的c 擁有的obj
// if (object->NeedsMark(mark)) {
// object->GetTableObject()->Mark(a_machine, mark);
// }
#endif
}
break;
case MC_THREAD_CREATE: {
// 線程創(chuàng)建時的回調
}
break;
case MC_THREAD_DESTROY: {
// 線程銷毀時的回調
}
break;
}
return false;
}
翻譯后記
前前后后翻譯了一周多, 終于算是告一段落了, 通過翻譯, 增加了對GameMonkey的一些理解.
因為它是從lua發(fā)展起來的, 有很多概念有一些像, 但是經過仔細的觀察和研究代碼, 發(fā)現(xiàn)GM除了借鑒了一些lua的概念, 從實現(xiàn)上和lua是完全不一樣的. 比如元方法的實現(xiàn)等等. GM使用起來將會感覺更加復雜, 有很多問題都需要去coding解決, 而不像lua那樣美麗:) 但是從另外一方面來講, GM的確是給了程序足夠的控制力, 的確稱的上是一門面向程序員的語言.
翻譯的比較匆忙, 有什么錯盡管指出:) 謝謝