This commit is contained in:
notify 2023-05-13 14:45:38 +08:00 committed by GitHub
parent 5843442f98
commit ad97c5d5f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 184 additions and 3 deletions

View File

@ -1,13 +1,14 @@
.. SPDX-License-Identifier: GFDL-1.3-or-later .. SPDX-License-Identifier: GFDL-1.3-or-later
Dev文档 开发过程的碎碎念
============ ================
某人写出来的整理思路用的文档= =
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
ai.rst ai.rst
compile.rst
database.rst database.rst
gameevent.rst gameevent.rst
gamelogic.rst gamelogic.rst
@ -17,3 +18,4 @@ Dev文档
todo.rst todo.rst
ui.rst ui.rst
hegemony.rst hegemony.rst
shendiaochan.rst

View File

@ -13,6 +13,7 @@
usr/index.rst usr/index.rst
diy/index.rst diy/index.rst
inner/index.rst
dev/index.rst dev/index.rst
api/index.rst api/index.rst
fkp/usr/index.rst fkp/usr/index.rst

View File

@ -3,6 +3,8 @@
编译 FreeKill 编译 FreeKill
============= =============
事先声明编译FK是个比较折磨的过程而且完全可以跳过直接看下一节除非你真的很想编译。
全平台通用步骤 全平台通用步骤
-------------- --------------

87
docs/inner/02-main.rst Normal file
View File

@ -0,0 +1,87 @@
从main函数开始
===============
FK说到底本质上是个C++项目所以程序的入口自然是在main.cpp的main函数中。
本文档不至于把代码一行行拿出来分析而是尽可能基于main函数来解答一些常见问题。
由于大家比较关注的点都是程序怎么开始执行Lua所以尽可能往这方面靠吧。
至于main函数的其他功能比如安卓版执行之前先复制文件之类的请大家自行查看吧。
服务器端相关
-------------
FK是一个遵循C/S架构的游戏所以它才支持联机游玩功能。
从文档可以知道 ``./FreeKill -s`` 就能启动服务端进程来看相应main代码吧。
.. code:: cpp
// main.cpp: 180行左右
Server *server = new Server;
if (!server->listen(QHostAddress::Any, serverPort)) {
qFatal("cannot listen on port %d!\n", serverPort);
上面的代码就是检测到命令行参数 ``-s`` 之后,开始启动服务器的代码。
这里创造了Server对象然后在相应端口监听。
不过如果你去看Server的构造函数的话却找不到Lua相关的代码。
这是因为在服务器端中,可能同时运行多个游戏房间,每个游戏房间是一个单独的线程,
所以是每个游戏房间维护一个Lua虚拟机。总体而言这是为了让各个房间之间的Lua
不产生冲突。
所以服务端只有在创建新房间的时候才会执行Lua来看Room的构造函数吧
.. code:: cpp
// room.cpp: 31行左右
L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua");
DoLuaScript(L, "lua/server/room.lua");
initLua();
首先 ``DoLuaScript`` 从神杀抄的就是个dofile函数的C++封装版本。
然后调查initLua函数
.. code:: cpp
// swig/server.i 31行左右
void Room::initLua()
{
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_getglobal(L, "CreateRoom");
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 1, 0, -2);
lua_pop(L, 1);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qCritical() << error_msg;
}
}
这个C++函数翻译成Lua就是
``pcall(function() CreateRoom(C++层面的Room) end, debug.traceback)``
意思就是以这个c++版的Room对象为参数执行Lua全局函数CreateRoom。
就不贴代码了在room.lua的底端自己看吧。
不过即使到了这一步,游戏还是没运行起来。
游戏运行函数是 ``Room::run()`` ,里面调用的那个函数已经给注释了。
至此服务端的Lua开始执行游戏逻辑也开始运行了。
至于这块是如何运行的相信熟悉Lua的各位能自己去阅读。
客户端相关
-----------
如果main函数中没有检测到 ``-s`` 命令行参数那么就绘制UI并显示一个窗口。
.. code:: cpp
// main.cpp: 292行左右
// 加载完全局变量后,就再去加载 main.qml此时UI界面正式显示
engine->load("qml/main.qml");
这里就加载了main.qml然后剩下就交给qml来处理UI了。至此main函数结束。
客户端的Lua就简单了在Client的构造函数里面。客户端的Lua代码只在需要时
执行而这个就不在main的讨论范畴了这个在后面通信模块会提到。

View File

View File

@ -0,0 +1,76 @@
协程机制
===========
FK的游戏逻辑是全Lua的这就有一个很大的问题Lua是单线程的那如果我要在
对局中做一些和游戏流程无关、但又不完全无关的操作比如预亮技能cpp可是对
技能一无所知的),是不是还要创另一个线程去执行?
由于所有游戏有关的数据全部在Lua所以去Cpp代码多弄个线程处理预亮、托管、旁观
等等操作并不可行。事实上这是通过协程机制实现的,请接着看。
协程简介
----------
我这里就简单的讲一下得了,关于协程的更多详情自己去查询吧。
协程其实就是一种特殊的调用函数的过程,他的特点就是可以随时通过
`` coroutine.yield `` 中止执行然后调用者也能通过resume来让中断的函数
继续执行。
通过协程不仅实现了这样的一个预亮处理机制等等,还实现了游戏事件机制,
利用协程可以实现事件的中断机制(结束本回合之类的)。
我们先来看预亮技能之类的行为是如何实现的吧。
处理客户端的request
--------------------
像托管、预亮等等行为称为“客户端对服务器发起的request”。
从通信机制一节中我们知道只有服务端才能发起request这里所谓客户端
request本质上只是对服务器的一种notify罢了。
在Room类Cpp类维护了一个请求队列用来保存这些来自客户端的请求。
而当Lua游戏逻辑有空休息时就把 **游戏逻辑协程** 暂时挂起yield然后再
切换到 **请求处理协程** 。关于种种请求的处理详情请查看request.lua。
这里来看看这两个协程是如何创建的,以及如何执行、如何切换。
首先是创建协程这个是在RoomLua类启动的时候创建的
.. 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吧。

13
docs/inner/index.rst Normal file
View File

@ -0,0 +1,13 @@
.. SPDX-License-Identifier: GFDL-1.3-or-later
深入了解FK
============
这是一系列深入到FK种种实现细节的文档其目的是能带着你大致的阅读和理解一些源代码。
.. toctree::
:maxdepth: 1
01-compile.rst
02-main.rst
04-coroutine.rst