• About
    • Resume
A Game Developer also plays some guitar

Tag Archives: Lua

新浪微博API for MOAI

November 13, 2012 1:54 pm / 3 Comments / Benny Chen

最近一个星期干了一件事情,因为游戏的需要,为Moai引擎集成了新浪微博的API,这样也算为开源又一次出了份力。

最近一年基本上是游走于游戏的脚本层,很久没有碰引擎代码了,这一周的工作充满了怀旧感,虽然复杂度不算高,但还是勾起了不少当年开发游戏引擎的记忆。

check it out from https://github.com/bennychen/moai-dev, branch: sina_weibo

同时该branch也集成了另一位Moai开发者的截屏API, from http://getmoai.com/forums/post4555.html

如何使用[sample code]


MOAISim.openWindow ( "test", 320, 480 )

viewport = MOAIViewport.new ()
viewport:setSize ( 320, 480 )
viewport:setScale ( 320, 480 )

layer = MOAILayer2D.new ()
layer:setViewport ( viewport )
MOAISim.pushRenderPass ( layer )

gfxQuad = MOAIGfxQuad2D.new ()
gfxQuad:setTexture ( "moai.png" )
gfxQuad:setRect ( -64, -64, 64, 64 )

prop = MOAIProp2D.new ()
prop:setDeck ( gfxQuad )
prop:setLoc ( 0, 80 )
layer:insertProp ( prop )

font = MOAIFont.new ()
font:loadFromTTF ( "arialbd.ttf", " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.?!", 12, 163 )

textbox = MOAITextBox.new ()
textbox:setFont ( font )
textbox:setRect ( -160, -80, 160, 80 )
textbox:setLoc ( 0, -100 )
textbox:setYFlip ( true )
textbox:setAlignment ( MOAITextBox.CENTER_JUSTIFY )
layer:insertProp ( textbox )

--MOAISinaWeiboIOS.init( "you app id", "you app secret", "your callback url" )

function openCompileDialog( txt, imgFileLoc )
	MOAISinaWeiboIOS.compileDialog( txt, imgFileLoc )
	MOAISinaWeiboIOS.setListener( MOAISinaWeiboIOS.DIALOG_POST_CANCEL_CLICKED, function()
		print( "user clicked cancel post" )
		MOAIFileSystem.deleteFile( imgFileLoc )
	end )
	MOAISinaWeiboIOS.setListener( MOAISinaWeiboIOS.REQUEST_RESPONSE_WITH_RESULT, function()
		print( "post successuflly" )
		MOAIFileSystem.deleteFile( imgFileLoc )
	end )
end


function test()
	local frameBounds={
		left=0, 
		top=0, 
		width=1024, 
		height=768
	}

    local tmpFilename=MOAIScreenShotIOS.snapshotToFile( MOAIScreenShotIOS.PORTRAIT, frameBounds )
    print( "Saved at location: ", tmpFilename );
	print( MOAIFileSystem.checkFileExists( tmpFilename ))

	if ( not MOAISinaWeiboIOS.isAuthValid() )
	then
		MOAISinaWeiboIOS.login()
		MOAISinaWeiboIOS.setListener( MOAISinaWeiboIOS.DIALOG_LOG_IN_CANCEL, function() 
			MOAIFileSystem.deleteFile( tmpFilename )
		end )
		MOAISinaWeiboIOS.setListener( MOAISinaWeiboIOS.SESSION_DID_LOGIN, function()
			print( "successfully logged in" )
			openCompileDialog( "test", tmpFilename )
		end )
	else
		print( "already logged in, post" )
		openCompileDialog( "test", tmpFilename )
	end
end

timer = MOAITimer.new()
timer:setSpan( 1 )
timer:setListener( MOAIAction.EVENT_STOP, test )
timer:start()

Posted in: Game Programming, iOS, Lua / Tagged: lua, Moai, weibo, 微博, 新浪

Another Thread @ Moai Forum

October 4, 2011 11:07 am / Leave a Comment / Benny Chen

Does Moai support an API like ‘setTimeout’?

http://getmoai.com/forums/moai-sdk-developer-support/does-moai-support-an-api-like-settimeout/

Me
Hi,
Does Moai support an API like JavaScript’s setTimeout, which schedules a function execution after a specified time. I noticed there is a Moai class called MOAITimer, so I’m wondering if this class could be used for this purpose? I know a ‘setTimeout’ function could be easily realized on Lua level using MOAISim:getElapsedTime() and Lua’s ‘pcall’ function, but I’m just lazy and wonder if there is a shortcut that Moai has provided?
Thank you~

Josh
Moai doesn’t support that, but for convenience I’ll share the Lua function I use for this exact purpose:
(Hide some code)
EDIT: updated while investigating bug mentioned below. Will ensure this code works once bug is fixed.

Me
Thank you for the sharing, but the function gives the following error after I run:

cannot use ‘…’ outside a vararg function near ‘…’

I think ‘…’ are also needed for “function()” in the 5th line, so I added it.
However, after I ran the following code

function printAStr( str )	
    print( str )
end
callWithDelay( 1, printAStr, "helloWorld" )

It prints “userdata: 0x3192c4” instead of “helloWorld”, any clue for this? I think the core issue is how to pass arguments to the callback function of timer’s listener?

Josh
I made two mistakes in my function, which I have corrected above. You are correct I forgot to pass the … properly, and I also forgot that the timer listener callback receives the timer as the first parameter, which is why you were seeing the userdata print out in your test.

Me
seems still unworkable for the arguments pass…
Ok, so my question is how to pass arguments to callback function of timer’s listener?

Josh
It looks like there may be a bug in the listener with the last release. I’ve opened a bug on the issue. I also realized yet another error in my above code, and have corrected it. Hopefully that’s the last edit I have to do. 🙂

Patrick
Thanks for finding the bug. Won’t be able to get to it for a day or so. Sorry for the trouble!

Patrick
OK, finally had time to look at this. It looks like it’s working correctly. The code I used is below.

MOAISim.openWindow ( "test", 320, 480 )
function callWithDelay ( delay, func, ... )
  local timer = MOAITimer.new ()
  timer:setSpan ( delay )
  timer:setListener ( MOAITimer.EVENT_TIMER_LOOP,
    function ()
      timer:stop ()
      timer = nil
      func ( unpack ( arg ))
    end
  )
  timer:start ()
end

function printAStr ( str ) print( str ) end
callWithDelay ( 1, printAStr, "helloWorld" )

Me
It works! Thanks a lot 🙂

Posted in: Game Programming, Lua / Tagged: callWithDelay, lua, Moai, setTimeout

为SyntaxHighlighter添加新语言

September 4, 2011 1:36 pm / 1 Comment / Benny Chen

因为经常要在博客里贴一些Lua代码,但是所使用的SyntaxHighlighter插件默认不支持Lua语言,所以去研究了一下如何为SyntaxHighlighter添加并激活一个新的语言,这里将过程和有同样需求的童鞋分享。(因为我添加的是Lua语言,下面的过程描述会以Lua为例,在添加你所需要的语言时,你只要将相应的项更换为你的自定义设置即可)

1. 从这篇博客里寻找所需要的语言:http://www.undermyhat.org/blog/2009/09/list-of-brushes-syntaxhighligher/;
2. 下载对应的shBrushXXX.js脚本,比如我下载的是shBrushLua.js,它看起来像这样:

SyntaxHighlighter.brushes.Lua = function()
{
	var keywords =	'break do end else elseif function if local nil not or repeat return and then until while this';
	var funcs = 'math\\.\\w+ string\\.\\w+ os\\.\\w+ debug\\.\\w+ io\\.\\w+ error fopen dofile coroutine\\.\\w+ arg getmetatable ipairs loadfile loadlib loadstring longjmp print rawget rawset seek setmetatable assert tonumber tostring';

	this.regexList = [
		{ regex: new RegExp('--\\[\\[[\\s\\S]*\\]\\]--', 'gm'),		css: 'comments' },
		{ regex: new RegExp('--[^\\[]{2}.*$', 'gm'),			    css: 'comments' },	// one line comments
		{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,     css: 'string' },    // strings
		{ regex: SyntaxHighlighter.regexLib.singleQuotedString,     css: 'string' },    // strings
		{ regex: new RegExp(this.getKeywords(keywords), 'gm'),		css: 'keyword' },	// keyword
		{ regex: new RegExp(this.getKeywords(funcs), 'gm'),		    css: 'func' },		// functions
		];
}

SyntaxHighlighter.brushes.Lua.prototype	= new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.Lua.aliases = ['lua'];

3. 使用FTP工具登陆到WordPress空间,进入到wp-content/plugins目录,新建一个目录,取一个有意义的名字,比如syntaxhighlighter-lua;
4. 将shBrushLua.js上传到新创建的目录;
5. 在该目录创建一个另一个shBrushLua.php文件,添加如下内容:

<?php
/*
Plugin Name: SyntaxHighlighter Evolved: Lua 
Description: Adds support for the Lua language to the SyntaxHighlighter Evolved plugin.
Author: Benny 
Version: 1.0.0
*/
 
// SyntaxHighlighter Evolved doesn't do anything until early in the "init" hook, so best to wait until after that
add_action( 'init', 'syntaxhighlighter_lua_regscript' );
 
// Tell SyntaxHighlighter Evolved about this new language/brush
add_filter( 'syntaxhighlighter_brushes', 'syntaxhighlighter_lua_addlang' );
 
// Register the brush file with WordPress
function syntaxhighlighter_lua_regscript() {
    wp_register_script( 'syntaxhighlighter-brush-lua', plugins_url( 'shBrushLua.js', __FILE__ ), array('syntaxhighlighter-core'), '1.1.1' );
}
 
// Filter SyntaxHighlighter Evolved's language array
function syntaxhighlighter_lua_addlang( $brushes ) {
    $brushes['lua'] = 'lua';
    return $brushes;
}
?>

6. 文件都准备完了,OK,进入到WordPress后台管理的Plugins下,应该能看到新添加的一项syntaxhighlighter-lua,激活它。

Done! It should work now!

其实新添加的js和php文件也可以放到SyntaxHighlighter插件本身的目录下,但是让它独立成插件的好处是,当SyntaxHighlighter升级时,你的个人配置不会因为覆盖而丢失。

Posted in: Some Experiences / Tagged: lua, new brush, plugin, syntaxhighlighter, wordpress, 新语言, 添加

基于Lua的State Pattern

September 4, 2011 12:56 pm / Benny Chen

代码来自于最近写的Pacman,更多请查看 – https://github.com/bennychen/Moai-based-Pacman

class.lua实现了在Lua中创建类的模拟,非常方便。class.lua参考自http://lua-users.org/wiki/SimpleLuaClasses

-- class.lua
-- Compatible with Lua 5.1 (not 5.0).

function class(base, init)
   local c = {}    -- a new class instance
   if not init and type(base) == 'function' then
      init = base
      base = nil
   elseif type(base) == 'table' then
    -- our new class is a shallow copy of the base class!
      for i,v in pairs(base) do
         c[i] = v
      end
      c._base = base
   end
   -- the class will be the metatable for all its objects,
   -- and they will look up their methods in it.
   c.__index = c

   -- expose a constructor which can be called by <classname>(<args>)
   local mt = {}
   mt.__call = function(class_tbl, ...)
   local obj = {}
   setmetatable(obj,c)

-- below 2 lines are updated based on the Comments from 'http://lua-users.org/wiki/SimpleLuaClasses'
--   if init then
--      init(obj,...)
   if class_tbl.init then
      class_tbl.init(obj,...)
   else 
      -- make sure that any stuff from the base class is initialized!
      if base and base.init then
      base.init(obj, ...)
      end
   end
   return obj
   end
   c.init = init
   c.is_a = function(self, klass)
      local m = getmetatable(self)
      while m do 
         if m == klass then return true end
         m = m._base
      end
      return false
   end
   setmetatable(c, mt)
   return c
end

State基类,包含三个stub函数,enter()和exit()分别在进入和退出state时被执行,onUpdate()函数将会在state被激活时的每帧被执行。

require "class"

State = class()

function State:init( name )
	self.name = name
end

function State:enter()
end

function State:onUpdate()
end

function State:exit()
end

StateMachine类,该类集成了Moai的MOAIThread类。MOAIThread类似于Lua中的coroutine,但是在Moai中被yield的MOAIThread,会在game loop的每帧中被自动resume,见StateMachine:updateState函数,利用此特点,来实现每帧执行State:onUpdate函数。

require "State"

StateMachine = class()

function StateMachine:init()
	self.currentState = nil
	self.lastState = nil
end

function StateMachine:run()
	if ( self.mainThread == nil )
	then
		self.mainThread = MOAIThread.new()
		self.mainThread:run( self.updateState, self )
	end
end

function StateMachine:stop()
	if ( self.mainThread )
	then
		self.mainThread:stop()
	end
end

function StateMachine:setCurrentState( state )
	if ( state and state:is_a( State ) )
	then
		if ( state == self.currentState )
		then
			print( "WARNING @ StateMachine::setCurrentState - " ..
				   "var state [" .. state.name .. "] is the same as current state" )
			return
		end
		self.lastState = self.currentState
		self.currentState = state
		if ( self.lastState )
		then
			print( "exiting state [" .. self.lastState.name .. "]" )
			self.lastState:exit()
		end
		print( "entering state [" .. self.currentState.name .. "]" )
		self.currentState:enter()
	else
		print( "ERROR @ StateMachine::setCurrentState - " ..
			   "var [state] is not a class type of State" )
	end
end

function StateMachine:updateState()
	while ( true )
	do
		if ( self.currentState ~= nil )
		then
			self.currentState:onUpdate()
		end
		coroutine.yield()
	end
end

如何利用State和StateMachine类的示例,首先定义两个state。
SampleState.lua

require "State"

State1 = class( State ) 

function State1:init()
	State.init( self, "State1" )
end

function State1:enter()
	self.i = 0
end

function State1:exit()
	self.i = 0
end

function State1:onUpdate()
	print( self.name .. " is updated" )
	self.i = self.i + 1
	print( "self.i=" .. self.i )
	if ( self.i == 10 )
	then
		print( state2 )
		SM:setCurrentState( state2 )
		self.i = 0
	end
end

-----------------------

State2 = class( State ) 

function State2:init()
	State.init( self, "State2" )
end

function State2:onUpdate()
	print( "State2 is updated" )
end

test.lua

require "StateMachine"
require "SampleState"

SM = StateMachine()
SM:run()
state1 = State1()
state2 = State2()
SM:setCurrentState( state1 )
Posted in: Game Programming, Lua / Tagged: finite state machine, FSM, lua, state pattern, 状态模式

Moai

August 21, 2011 10:24 am / Leave a Comment / Benny Chen

最近通过澳洲朋友Andrew的介绍,开始使用了一款正在开发中的开源游戏引擎Moai – the mobile platform for pro game developers.

Moai是一款由’Zipline Games’公司开发的2D游戏引擎,我写这篇文章时的版本是0.5 Beta。它最大的特点就是集成了Lua脚本语言,它提供了一系列class-based的Lua API。开发者一般只需要通过写Lua脚本,即可实现一款游戏。而引擎本身解决了跨平台的问题,开发完的游戏可以顺利的发布到iOS和Android平台。而引擎本身提供的功能上,也基本上覆盖了需要开发一款2D游戏所有的元素,设备和输入,2D Sprite,动画,字体,粒子系统,物理,声音等。Moai的另外一个重要特色是提供了一个它称之为Moai Cloud的云服务,对于这个我还没有深研究,不过据称它可以让需要后端server的游戏变得简单,用户同样只需要用Lua来编写server的逻辑代码,而至于像scale-up这样的问题完全可以交给Moai引擎来处理。

然而开源引擎,尤其是处于Beta测试中的开源引擎,想用于开发正式的游戏确实有些不可靠。我最近在把之前写过的一个Pacman 2D移植到Moai上,但是遇到了很多的问题,并且一些问题最终通过层层纠结后证实是引擎本身的问题。虽然过程坎坷,但因为参与开源引擎项目,并有了自己的contribution,这本身着实是一件令人欣慰而振奋的事情。

但是希望Moai的开发者们work harder,让Moai尽快变得更好 😀

注册并获取Moai:http://dashboard.moaicloud.com/

Moai入门文章:
第一部分:http://getmoai.com/2011/04/moai-basics-part-1/
第二部分: http://getmoai.com/2011/04/moai-basics-part-2-2/

Moai API Documentation:http://getmoai.com/docs/

Posted in: Game Programming / Tagged: 2d, beta, cloud, game engine, lua, Moai, mobile, zipline games, 开源

Lua相关问题整理(4) – 让Lua的eval函数支持赋值语句

November 28, 2010 12:22 pm / Leave a Comment / Benny Chen

上一篇文章提到了在Lua中实现类似于JavaScript中的eval函数,遗憾是该eval函数不支持赋值语句,原因是Lua的赋值运算符是不支持返回值。所以如果要让该eval函数也支持赋值语句,就需要一个额外的工作,让它鉴别一个语句是不是赋值语句,如果是,则return的是被赋值后变量的值。为此,我写了一个isAssignmentExpression函数,比较粗糙,不过够用了,基本思想是检测语句中第一个出现的”=“操作符,且该”=“不能在一对引号当中。

-- Lua code
function isAssignmentExpression( str )
	local i = 1;
	local curChar;
	local quotesType = "none" -- none, single or double
	local isEscaping = false

	curChar = string.sub( str, 1, 1 )
	while ( curChar ~= "" ) do
		if ( curChar == "'" and
			 isEscaping == false and
			 quotesType ~= "double" )
		then
			if ( quotesType == "single" )
			then quotesType = "none"
			elseif ( quotesType == "none" )
			then quotesType = "single"
			end
		end

		if ( curChar == """ and
			 isEscaping == false and
			 quotesType ~= "single" )
		then
			if ( quotesType == "double" )
			then quotesType = "none"
			elseif ( quotesType == "none" )
			then quotesType = "double"
			end
		end

		if ( curChar == "\" and isEscaping == false )
		then isEscaping = true
		else isEscaping = false
		end

		if ( curChar == "=" and quotesType == "none" )
		then
			if ( string.sub( str, i+1, i+1 ) ~= "=" )
			then
				return true, string.sub( str, 1, i - 1 )
			else
				return false
			end
		end

		i = i + 1
		curChar = string.sub( str, i, i )
	end

	return false
end

function eval( str )
	local bAssign
	local var
	bAssign, var = isAssignmentExpression( str )
	if ( bAssign )
	then
		print( "Assignment, var=" .. var )
		loadstring( str )()
		return loadstring( "return " .. var )()
	else
		return loadstring( "return " .. str )()
	end
end

-- 以下是一组测试
print( eval( "3+4" ) )
-- 7
function Multiply( a, b )
	return a*b
end
print( eval( "Multiply( 3, 4 )" ) ) 
-- 12
print( eval( "i" ) ) 
-- nil
print( eval( "i = 1" ) ) 
-- Assignment, var=i    
-- 1
print( eval( "i = i + 1" ) ) 
-- Assignment, var=i    
-- 2
print( eval( "i" ) ) 
-- 2
print( eval( "i+1" ) ) 
-- 3
print( eval( "i, j = 4, 5" ) ) 
-- Assignment, var=i, j    
-- 4	   5
print( eval( "i = {}" ) ) 
-- Assignment, var=i    
-- table: 003CD818
print( eval( "i[ "0" ] = 0" ) ) 
-- Assignment, var=i[ "0" ]     
-- 0
print( eval( "i[ "\"0=" ] = 1" ) ) 
-- Assignment, var=i[ ""0=" ]     
-- 1
print( eval( "i.name="hello"" ) ) 
-- Assignment, var=i.name     
-- hello
print( eval( "i[0], i.name = 4" ) ) 
-- Assignment, var=i[0], i.name     
-- 4	  nil
print( eval( "i == 10" ) ) 
-- false
Posted in: Game Programming, Lua / Tagged: eval, lua, 赋值语句

Lua相关问题整理(3)

November 27, 2010 5:04 pm / Leave a Comment / Benny Chen
  1. 在注册给Lua的C函数中为Lua提供默认参数

    使用luaL_optstring, luaL_optnumber, luaL_optinteger等Lua API,如下示例,函数有一个默认字符串参数,默认值为””,这样在Lua中调用whatever的时候,whatever()或者whatever( “whatever”)均可。(Oh…whatever…随便…都行…)

    // C code
    int Whatever( lua_State *L )
    {
    	string str = luaL_optstring( L, 1, "" );
    	//...omitted code...
    }
    lua_register( L, "whatever", Whatever );
    
  2. 建立Lua字符串到C enum的映射

  3. 使用luaL_checkoption这个Lua API,它可以把从Lua传来的string转换为相应的C string array中的index,从而可以建立Lua字符串和C enum的映射。以下是个简单的示例:

    // C code
    enum PlayerType
    {
    	PLAYER_TYPE_UNDEFINED = -1,
    	PLAYER_TYPE_KING = 0, // 主公
    	PLAYER_TYPE_INSURGENT, // 反贼
    	PLAYER_TYPE_LOYAL, // 忠臣
    	PLAYER_TYPE_TREACHEROUS, //内奸-_^
    	NUM_PLAYER_TYPE // just a sentinel
    };
    
    const char * const PlayerTypeList[NUM_PLAYER_TYPE + 1] =
    {
    	"KING",
    	"INSURGENT",
    	"LOYAL",
    	"TREACHEROUS",
    	NULL
    };
    
    static int testPlayerType( lua_State *L )
    {
    	PlayerType type = static_cast< PlayerType >( 
    						luaL_checkoption( L, 1, 
    						"INSURGENT", PlayerTypeList ) );
    	std::cout << "Type index is " << type 
    		      << " - " << PlayerTypeList[type] 
    		      << std::endl;
    	return 0;
    }
    
    lua_register( L, "setPlayerType", setPlayerType )
    

    首先enum PlayerType定义了一组角色类型,来自人人都爱的三国杀:-)。

    接着PlayerTypeList定义了一个字符串数组,给Lua使用。注意需要保证enum和字符串数组的对应,比如PlayerTypeList[PLAYER_TYPE_KING]是“KING”,同时,PlayerTypeList必须以NULL结尾。

    在定义给Lua的函数testPlayerType中,就可以用luaL_checkoption将Lua传来的字符串参数转换为相应enum的值。luaL_checkoption还支持默认参数,比如在上面例子中,将第三个参数设为“INSURGENT”,如果Lua中没有提供任何参数,则PlayerType就为与“INSURGENT”相对应的PLAYER_TYPE_INSURGENT。

    以下是一组测试及结果:

    --Lua code
    testPlayerType( "KING" ) -- Type index is 0 - KING
    testPlayerType() -- Type index is 1 - INSURGENT
    testPlayerType( "whatever" ) -- bad argument #1 to 'testPlayerType' (invalid option 'whatever')
    
  4. 在Lua中实现eval函数

    众所周知,JavaScript中有一个著名的eval函数,它用于把一个字符串当作一段JS代码去执行,在Lua中没有提供类似的函数,但稍微包装下Lua的库函数loadstring即可实现,以下是代码。

    --Lua code
    function eval( str )
    	local func = loadstring( "return " ..str );
    	return func()
    end
    

    这样已经可以了,不过相比于JS的eval函数,功能稍微差一些,因为它不支持赋值语句,这是Lua语言天然的原因,因为Lua的赋值运算符没有返回值,在其他语言中常见传递赋值的“i=j=1”(先赋值j=1,然后将(j=1)的返回值j赋值给i),在Lua中是不允许的。所以当eval执行的是赋值运算(比如i=1)的时候,return i=1就会出错。

    下面是一些测试例子:

    --Lua code
    print( eval( "3+4" ) ) -- OK, 打印7
    
    function Multiply( a, b )
    	return a*b
    end
    
    print( eval( "Multiply( 3, 4 )" ) ) -- OK,打印12
    
    print( eval( "i = 1" ) ) -- 错误, attempt to call a nil value
    i = 1
    print( eval( "i" ) ) -- OK,打印1
    print( eval( "i = i + 1" ) ) -- 错误, attempt to call a nil value
    
  5. 实现luaL_checkbool

    不知道为什么Lua的API没有提供luaL_checkbook函数,不过很容易实现:

    // C code
    BOOL luaL_checkbool( lua_State *luaVM, int numArg )
    {
    	BOOL b = FALSE;
    	if ( lua_type( L, numArg ) == LUA_TBOOLEAN )
    	{
    		b = lua_toboolean( L, numArg );
    	}
    	else
    	{
    		luaL_typerror( L, numArg, lua_typename( L, LUA_TBOOLEAN ) )
    	}
    	return b;
    }
    
Posted in: C++, Game Programming, Lua / Tagged: enum, eval, lua, luaL_checkbool, 默认参数

Lua相关问题整理(2)- 如何在C中为Lua提供同步调用接口

November 7, 2010 10:25 pm / Leave a Comment / Benny Chen

这个问题的具体描述是——C注册给Lua一个函数,但Lua调用该C函数并不能立即获得结果(比如需要访问远程服务器获取值),如何能让Lua停止并等待,直到获取到结果后,才继续执行接下来的脚本。

举个例子,比如说有一个C函数login,我们试图通过调用该函数以执行用户的登录操作并获取验证结果。首先看下面这段C代码:

// C code
int login( lua_State *L )
{
	string user = luaL_checkstring( L, 1 );
	string password = luaL_checkstring( L, 2 );

	// 该函数将user和password发送到服务器后立即返回,
	// 绝不要在此处阻塞,这将严重影响效率
	sendAuthenticationInfo( user, password );

	return 0;
}
// 注册给Lua
lua_register( L, "login", login );

可以看到,因为该函数需要访问远程的登录服务器,在系统中一般都采取异步操作(让系统阻塞等待结果是不可接受的)。但是这样Lua开发人员调用login函数时,也只能异步等待结果,下面是Lua中处理登录操作的代码。

-- Lua code
-- 当用户点击“登陆”按钮后,执行该函数
function onClickLoginBtn()
	local user = ...
	local password = ...
	login( user, password ) -- 只是发送login命令
end

--当获取login结果后的回调函数
function onGetLoginResult( bPass )
	if bPass == true then
		print( "Authentication succeeded." )
		...omitted code...
	else
		print( "Authentication failed." )
		...omitted code...
	end
end

这段Lua代码很好理解,首先onClickLoginBtn是一个按钮事件处理函数,当用户点击了”登录“按钮后,会触发该函数。该函数首先从界面上获取用户输入的user和password,然后调用了上面C中所定义的login函数。正如前面的C代码所写的,login函数不会马上得到认证结果,所以执行后马上退出。另一个函数是onGetLoginResult,这个Lua函数需要当C系统中获取到认证结果后被回调执行,以真正执行login的后续操作。所以,我们还需要在C中添加回调的代码。

// C code
// 当服务器端返回认证结果后
BOOL loginResult = ...
lua_getglobal( L, "onGetLoginResult" );
lua_pushboolean( L, loginResult );
lua_call( L, 1, 0 );

在这里,我们在C中hardcode了回调onGetLoginResult的代码,这很丑陋,不过可以避免,比如可以给前面注册给Lua的login函数增加一个参数callbackFunctionName,以让Lua显式的告诉系统当获取登录结果后的回调函数名称。

再次回到本文一开始所提出的问题,尽管在系统中的异步调用不可避免,但我们希望在Lua中能够有同步机制,即Lua脚本在得到验证结果后才允许被继续执行,如何才能做到这一点呢。

Lua有一个非常棒的coroutine机制,在Lua代码中可以通过协同程序来进行多线程,可以使用coroutine.yield和coroutine.resume来对协同程序进行挂起和恢复。需要达到上面所提出的目标,只需在系统中使用与coroutine.yield和coroutine.resume相对应的Lua C API——lua_yield和lua_resume即可。如下所示,login函数有些小改变:

// C code
int login( lua_State *L )
{
	string user = luaL_checkstring( L, 1 );
	string password = luaL_checkstring( L, 2 );
	sendAuthenticationInfo( user, password );
	return lua_yield( L, 0 );
}
// 注册给Lua
lua_register( L, "login", login );

可以看到,与前面的区别只有login函数的最后一句,调用了lua_yield后再返回,而不是return 0,这样就可以达到阻塞Lua脚本的目的。事实是,lua_yield是一个比较特殊的函数,它只能作为注册的C函数的返回值使用,否则调用失败。

当系统获得服务器端的登陆验证结果后,通过lua_resume即可恢复之前被阻塞的Lua。

// C code
// 当服务器端返回认证结果后
BOOL loginResult = ...
lua_pushboolean( L, loginResult );
lua_resume( L, 1 );

上面的代码是当你的系统中只有一个Lua虚拟机的情形,如果使用了多个Lua虚拟机,事情稍微有一点点复杂,login函数还需要将当前调用的lua_State存储下来,以便lua_resume的时候,可以知道恢复的是哪一个被阻塞的虚拟机。

OK,当C中有这样的实现后,Lua程序人员将会为此而高兴,因为Lua代码变得如此简洁。

-- Lua code
-- 当用户点击“登陆”按钮后,执行该函数
function onClickLoginBtn()
	local user = ...
	local password = ...
	if login( user, password ) == true then
		print( "Authentication succeeded." )
		...omitted code...
	else
		print( "Authentication failed." )
		...omitted code...
	end
end

系统or平台开发人员应当尽可能的为用户考虑,正如上面第二种解决方法所追求的那样。

Posted in: C++, Game Programming, Lua / Tagged: C++, coroutine, login, lua, resume, yield, 同步调用

Lua相关问题整理(1)

November 7, 2010 10:17 pm / Leave a Comment / Benny Chen
  1. (Under Linux)cannot find ‘dlsym’ ‘dlopen’ ‘dlerror’ ‘dlclose’

    需要同时链接”dl“库

  2. (Under Linux)编译lua报错luaconf.h:275:31: error: readline/readline.h: No such file or directory

    需要下载并安装GNU Readline Library

  3. PANIC: unprotected error in call to Lua API (unable to get ModuleFileName)

    1: 不推荐的解决方式:将Project Properties->Configuration Properties->General下的Character Set从unicode改成multi-set;
    2: 彻底的解决方式,参考此链接:http://lua-users.org/lists/lua-l/2006-06/msg00427.html

  4. 如何将Lua文本文件转化为Lua块文件(chunk file)

    调用LuaAPI – lua_dump

    关于lua_dump: about lua_dump: Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces a binary chunk that, if loaded again, results in a function equivalent to the one dumped. As it produces parts of the chunk, lua_dump calls function writer (see lua_Writer) with the given data to write them.

  5. 如何在C中调用Lua脚本层的库函数

    void BeginLuaLibCall( lua_State *L, const string &libName, const string &functionName )
    {
         lua_pushstring( L, libName.c_str() );
         lua_gettable( L, LUA_GLOBALSINDEX );
         lua_pushstring( L, functionName.c_str() );
         lua_gettable( L, -2 );
    }
    
    void EndLuaLibCall( lua_State *L )
    {
         lua_pop( L, -1 );
    }
    

    示例:调用table.getn(该函数用来获取一个table的size)

    // 利用上面的函数
    BeginLuaLibCall( L, "table", "getn" );
    // 假设你的脚本中有一个table变量myTable,获取它到栈顶
    lua_getglobal( L, "myTable" ); 
    // 执行table.getn( t )
    lua_call( L, 1, 1 ); 
    // 打印结果
    std::cout << lua_tonumber( L, -1 ) ) << std::endl;
    // 将结果弹出栈
    lua_pop( L, -1 );
    // 将名叫“table”的table弹出栈
    EndLuaLibCall( L);
    
Posted in: Game Programming, Lua / Tagged: chunk, dl, function, library, lua, readline

让管理lua_State的类指针安全

August 15, 2010 4:35 pm / 1 Comment / Benny Chen

最近因为在公司的项目中接手了Lua脚本模块的开发,所以研究了很多关于Lua脚本的东西,秉着“多看多想多记”的原则,我时刻敦促自己要及时记录下遇到的一些问题和想法。

在Lua中,几乎所有的东西都是围绕着lua_State转,所以,一般我们都会写一个类来封装管理它,比如:

class LuaObject
{
public:
	LuaObject()
	{
		m_L = luaL_newstate();
		luaL_openlibs( m_L );
	}
	~LuaObject()
	{
		if ( m_L )
		{
			lua_close( m_L );
			m_L = NULL;
		}
	}

private:
	lua_State *m_L;
};

这很好,不过它不是指针安全的。试想,如果一个LuaObject对象被复制,结果将会怎样。

LuaObject luaObject1;
LuaObject luaObject2( luaObject1 );

上面这段代码将会导致运行时crash,因为luaObject1和luaObject事实上指向了同一块lua_State,这样当luaObject1和luaObject2被析构时,lua_State会被两次lua_close,这不crash才怪呢!

事实是,当一个类包含了一个指针时,我们就需要开始变得格外谨慎,除了在构造函数和析构函数中要处理指针的初始化和清理外,我们还需要考虑深拷贝(deep copy),浅拷贝(shallow copy))的问题。如果使用编译器默认生成的拷贝构造函数,它只会浅拷贝指针,而指针所指向的内存区域不会被拷贝。就像上面一样,两个LuaObject实则共享了一个lua_State。

那该如何处理让管理LuaObject类的指针安全呢,深拷贝?厄,首先我也没有深究深度拷贝lua_State具体该如何完成,不过我猜想这可是一个复杂而重型的操作,仅仅为了带来指针安全而选择此可不是一个明智的选择。

如果你是开发LuaObject类的程序员,也许你会对使用LuaObject的程序员(或许有时使用者就是你自己)说:“你不要对它进行拷贝操作不就OK了”,但这是一种严重不负责任的行为,因为这种皮球会越踢越远的。比如,程序员A使用了你的LuaObject类,他写了一个包含LuaObject的指针的类,同样A也不考虑拷贝指针安全问题,然后A又将它的类传递给了B,B封装了A的类指针,然后传递给了C,接着如此重复,C再给D,D再给E,最后E在对类进行拷贝操作时,程序crash掉了,因为在某个最底层,他们共享了lua_State。不过因为这时候已经嵌套了这么多层,E程序员或许根本不知道也不关心什么是LuaObject(他只跟D的类打交道)。这时,要想追踪到BUG之源——万恶的不考虑指针安全的LuaObject,已经非常困难了,God bless you all.

事实是,如果你不想让你的类被复制,你就应该明确而显示的禁止它。其实lua_State的这个问题非常类似于Effective C++书中的第14个条款中所提到的问题:在资源管理类中小心copying行为。对于这个问题,书中提供了两种解决方案。第一种就是禁止复制。第二种则是对底层资源的“引用计数法”(reference count)。

第一种禁止复制很简单。(关于Uncopyable)

class LuaObject : private Uncopyable
{
public:
    ...
}

第二种则是使用智能指针来封装lua_State,我们可以使用boost的shared_ptr来封装lua_State,即shared_ptr< lua_State > m_L。如何对m_L进行初始化是另一个需要注意的问题,如果使用shared_ptr的常见初始化形式:m_L = shared_ptr< lua_State >( luaL_newstate() ),这样是不对的,因为在这种形式下,当lua_State的计数变为0时,shared_ptr会去调用lua_State的的析构函数,这显然是错误的,对lua_State的释放动作是lua_close而不是删除。事实上,这样编译器也无法通过,如果这么写,会报出“use of undefined type ‘lua_State’”的错误,提示lua_State是一个非完成的类型(incomplete type)。

我们应该为shared_ptr的初始化传入一个删除器(deleter)。很显然,lua_State的deleter是lua_close()函数,这样最终的代码如下。

#include <boost/shared_ptr.hpp>
using boost::shared_ptr;

class LuaObject
{
public:
	LuaObject()
	{
		m_L = shared_ptr< lua_State >( luaL_newstate(), lua_close );
		luaL_openlibs( m_L.get() );
	}

private:
	shared_ptr< lua_State > m_L;
};

这样再回到前面拷贝的那个例子,luaObject1和luaObject2共同引用了一个lua_State,但因为使用了shared_ptr,所以只有在lua_State的引用次数变为0时,它的deleter(这里是lua_close)才会被调用,安全了!

在我的项目中,因为没有使用boost库,也没有提供任何智能指针,所以使用禁止复制来保证安全。

Posted in: C++, Game Programming / Tagged: boost, copy, lua, lua_State, pointer, safe, shared_ptr, 安全, 指针

LinkedIn

Milan Petrovic

Categories

  • In My Life (25)
    • A Day in the Life (8)
    • English Learning (2)
    • Learn a Word (7)
    • Something In The Way (8)
  • Music Heaven (8)
    • Guitar (1)
    • In Concert (1)
    • Lyrics (3)
  • OK Computer (54)
    • 3D (3)
    • C++ (10)
    • Computer Graphics (15)
    • Game Programming (23)
    • iOS (6)
    • Linux (1)
    • Lua (9)
    • My Projects (3)
    • Some Experiences (9)
    • Talking in Code (2)
    • Unity (2)
  • Quotations (2)
  • Uncategorized (1)
  • Visca Barça (24)
    • FCB BJ (5)

Recent Posts

  • [译]优化你的手机游戏(没有延迟的,才是健康的)- 一篇给游戏美术设计师读的文章
  • 新浪微博API for MOAI
  • 稍后继续
  • Unity Developer ++
  • Another Thread @ Moai Forum
  • 1st Day of Golden Week
  • 为SyntaxHighlighter添加新语言
  • 基于Lua的State Pattern
  • Class Diagram of Pacman
  • 基于Moai的Pacman

Recent Comments

  • 约修亚_RK on 为SyntaxHighlighter添加新语言
  • 爱装的小男孩 on 小心DLL链接静态库时的内存错误
  • happyfire on Game Loop的几种实现方式
  • William on 新浪微博API for MOAI
  • Benny Chen on 新浪微博API for MOAI
  • your man on 新浪微博API for MOAI
  • 你家男人 on 稍后继续
  • 逍遥 on 关于对std::vector的遍历
  • papa on Unity Developer ++
  • T客网 ︱ Techpot » Blog Archive » iOS开发与OpenGL ES相关问题整理(1) on iOS开发与OpenGL ES相关问题整理(1)

Tags

2d 3D 3dsmax 3ds max air Apply architecture Asia tour barca Beijing bilbao binary search blocked bob boost bruce springsteen C++ capo CGContextDrawImage Champions League Change DLL DX10 eval exporter flash framework frustum culling game game engine iniesta ios linux lua Moai opengles pacman plug-in plugin 北京 导出插件 崩溃 巴萨 游戏引擎 踢球
© Copyright 2026 - A Game Developer
Infinity Theme by DesignCoral / WordPress