77 lines
3.4 KiB
ReStructuredText
77 lines
3.4 KiB
ReStructuredText
协程机制
|
||
===========
|
||
|
||
FK的游戏逻辑是全Lua的,这就有一个很大的问题:Lua是单线程的,那如果我要在
|
||
对局中做一些和游戏流程无关、但又不完全无关的操作(比如预亮技能,cpp可是对
|
||
技能一无所知的),是不是还要创另一个线程去执行?
|
||
|
||
由于所有游戏有关的数据全部在Lua,所以去Cpp代码多弄个线程处理预亮、托管、旁观
|
||
等等操作并不可行。事实上这是通过协程机制实现的,请接着看。
|
||
|
||
协程简介
|
||
----------
|
||
|
||
我这里就简单的讲一下得了,关于协程的更多详情自己去查询吧。
|
||
|
||
协程其实就是一种特殊的调用函数的过程,他的特点就是可以随时通过
|
||
`` coroutine.yield `` 中止执行,然后调用者也能通过resume来让中断的函数
|
||
继续执行。
|
||
|
||
通过协程不仅实现了这样的一个预亮处理机制等等,还实现了游戏事件机制,
|
||
利用协程可以实现事件的中断机制(结束本回合之类的)。
|
||
|
||
我们先来看预亮技能之类的行为是如何实现的吧。
|
||
|
||
处理客户端的request
|
||
--------------------
|
||
|
||
像托管、预亮等等行为,称为“客户端对服务器发起的request”。
|
||
从通信机制一节中我们知道只有服务端才能发起request,这里所谓客户端
|
||
request本质上只是对服务器的一种notify罢了。
|
||
|
||
在Room类(Cpp类)中,维护了一个请求队列,用来保存这些来自客户端的请求。
|
||
而当Lua游戏逻辑有空休息时,就把 **游戏逻辑协程** 暂时挂起(yield),然后再
|
||
切换到 **请求处理协程** 。关于种种请求的处理详情请查看request.lua。
|
||
这里来看看这两个协程是如何创建的,以及如何执行、如何切换。
|
||
|
||
首先是创建协程,这个是在Room(Lua类)启动的时候创建的:
|
||
|
||
.. code:: lua
|
||
|
||
-- room.lua: 70行左右,代码过长改为伪代码
|
||
local main_co = coroutine.create(function() self:run() end)
|
||
local request_co = coroutine.create
|
||
(function(rest) self:requestLoop(rest) end)
|
||
while not game_finished do
|
||
resume(main_co)
|
||
-- 如有错误则处理错误
|
||
resume(request_co)
|
||
-- 如有错误则处理错误
|
||
end
|
||
|
||
从这个无限循环可以看出,在主协程挂起之后,就会去处理请求处理协程。那个协程也
|
||
挂起之后,又开始新一轮循环,再去进入主协程。
|
||
|
||
主协程何时挂起?
|
||
-----------------
|
||
|
||
执行delay函数,或者正在waitForReply的时候。这两个函数会给服务端留下空闲的
|
||
时间,因此主协程会在此时挂起。
|
||
|
||
全局搜索一下 ``__handleRequest`` 即可知道主协程所有挂起的时机。
|
||
|
||
调查代码可知,request处理协程每次处理一条request就立刻挂起,而请求处理都是
|
||
一些耗时很短的操作。所以这样就可以在不影响游戏逻辑的前提下暂时中断,执行一
|
||
些其他的操作。而他们本质上不是并行执行的,所以也无需操作加锁之类的问题。
|
||
|
||
用协程实现事件机制
|
||
-------------------
|
||
|
||
Fk的事件机制也是用协程实现的。每当启动一个新事件后,实际上就创建了一个新的协程
|
||
并执行,还有一些别的什么处理逻辑等等。因为是在协程中实现的,这就有几个好处:
|
||
|
||
1. 当发生error时,被杀死的仅仅只是这个协程,而不是所有Lua代码。
|
||
2. 能随时用Yield函数去中断本协程,因此可以实现“终止事件”。
|
||
|
||
具体请自行查阅gameevent.lua吧。
|