您的位置:首页 > 编程语言 > Lua

异步(延时)逻辑难题,以及采用lua的解决方法

2012-05-11 13:56 190 查看
        在网游程序里混过一阵子的程序员大都知道,“异步逻辑”是游戏逻辑里最容易失误的地方之一。刷钱、刷经验、不花钱得到道具,然后关服、回档、删号等等等等,其可能造成的危害不胜枚举。而且实际上银行系统之类的地方遇到这种问题就更有趣了:)。

        不同团队对此类问题的称呼不同,我喜欢称其为“异步”。它是说这样一类问题:

        玩家满足某条件时(比如身上有10金币),与NPC对话触发一个对话选项:花10金获得一个道具。玩家选择此选项后,失去10金,获得道具。问题出在:逻辑上看这是一个连续的事件,但是由于玩家点击是一个延时操作(我们称为“异步”),程序里必然要有两个入口:

        1、玩家与NPC对话——判断金钱数,打开NPC对话框

        2、玩家选择了交换选项——(再次判断金钱数),扣除金币,给予道具

        这个例子很简单,关键是玩家选择交易的时候,已经是一段时间之后了,这段时间玩家的金钱很可能已经变化了。刚开始写逻辑程序的人,往往会忘记“再次判断金钱”这一步,造成后续逻辑错误,同样的没经验的测试人员也往往漏测这步,此BUG放出去的风险取决于具体逻辑,不可预计。

        大部分情况下,需要判断的地方比上面的例子要隐蔽。例如:

        玩家在特定场景使用收费道具,触发逻辑:3秒后召唤落石。那么3秒后主逻辑就会调用召唤落石的实现函数,而这个函数被调用的时候有几种情况:

        1、之前的这个玩家已经下线,函数应当判断后退出。

        2、玩家已经切换到了另一个场景,这个情况比较危险,因为如果没有判断场景,那么玩家就会在另一个场景使用落石技能。这个例子确实的发生在了某款游戏中:),而且这么有趣的事情必然在玩家之间传播的很快。

        3、玩家被定身无法使用落石技能。这种情况和具体设计有关,如果要完全解决会比较复杂,不光是这一个道具的问题了。

        逻辑程序员在犯过几次类似的错误之后,会抱怨——这种逻辑太容易写错了。我现在依然有这种感觉。而这种问题其实用Lua、Python等等支持“函数重入”的语言来说有很好的解决方法。其中Lua可以使用协程coroutine,Python用yield和signal语法就可以了。多说无益,贴代码:

g_condition = 0
-- 检查函数
function check_condition()
if g_condition == 0 then
print('Check Failed.')
return false
else
print('Check OK.')
return true
end
return false
end
-- 我们的具体某个操作operate,分成三步走,每一步打印信息提示
function operate()
if ( not check_condition() ) then
return
end
print('operating...1')
local par = coroutine.yield()

if ( not check_condition() ) then
return
end
print('operating...2')
par = coroutine.yield()

if ( not check_condition() ) then
return
end
print('operating...3')
return 'finished'
end

-- 以下模拟主逻辑用于测试
co = coroutine.create(operate)

print('Test 1: Normal procedure')
g_condition = 1
while true do
status, value = coroutine.resume(co, 'hahaha')
print('coroutine:',status, value)
if not status or value=='finished' then
break
end
end

print('---------------------------')
print('Test 2: The environment changes in the procedure ')
g_condition = 1
co = coroutine.create(operate)
g_condition = 1
status, value = coroutine.resume(co, 'hahaha')
print('coroutine:',status, value)
g_condition = 0
status, value = coroutine.resume(co, 'hahaha')
print('coroutine:',status, value)

status, value = coroutine.resume(co, 'hahaha')
print('coroutine:',status, value)



        说明:我们的operate要求某个环境变量(可以是玩家金钱、任务标记等等)不等于0,如果是0则终止此操作,而operate本身无法连续执行,比如需要提出扣信用卡的请求、等待玩家确认等等,所以操作不得不被分成三步。

        上面的例子基本演示清楚了整个思路。写operator的程序员思路可以保持连贯,然后在必要的地方插入中断yield,再加上判断函数,一个可能很健壮的流程就被写好了!

        现实情况肯定比这个复杂。有趣的地方在于:

        1、如何抽象出好用的判断函数?相信一个项目里有许多判断都是类似的可以复用的。

        2、由于yield函数的返回值可以由主逻辑从外围送进来,yield也可以把参数送到外面去。那么我们就多了一个内外交互的手段。用这个手段,我们可以很好的实现时间回调接口;我猜还会有其他很多神奇的功能,只要你想做。

        3、很多时候,遇到检查失败的时候,不是简单返回,而是要做rollback,消除之前的影响。这就比较麻烦了。个人建议一方面在项目里提供规范的回滚方法,另一方面要尽量避免这种情况,实际上大多逻辑问题很容易避免回滚(逻辑模块不要把破碎的接口注册给上层脚本去组织,而应该提供尽量方便的要么成功、要么失败的一锤子买卖的接口:)    )

        简单有趣的事情,不宜讲的太多,相信每个乐在其中的程序员都会给出不同的具体解决方案。我也不罗嗦了,赶紧写代码去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息