Lua 游戏开发学习

Lua 游戏开发学习

使用 Lua 带来的便利性

个人经历过的大部分游戏服务器项目, 用到 Lua 的地方都不多, 借着这次重构项目的机会, 可以好好学习一下. Lua 在游戏开发中有很多优点:

  • 业务逻辑开发效率的提升, 这点在业务复杂的 mmo 项目中尤为明显.
  • 摆脱繁重的数据配置, 当然策划是基本指望不上的.
  • 天然的热加载机制, 无论是配置还是业务逻辑, 都可以很方便的做不停服更新.
  • Lua 虚拟机提供了一个安全的沙箱, 增强了服务器的稳定性, 至少不用太过于担心线上的 coredump.
  • Lua 语言提供的 coroutine 机制, 可以很容易的维护多状态的异步逻辑, 比起多线程 C/C++ 来的更轻量.

Anyway, 不管 Lua 宣称多高效, 它毕竟是一个脚本, 不可能和 C/C++ 达到一样数量级的处理速度 (最理想的情况下, LuaJit 可以达到接近 C++ 1/10 - 1/5 的性能). 所以需要在设计时多加斟酌, 尽量把 CPU 消耗型的操作放到 C/C++ 层去处理.

Lua 的加载路径

Lua 的加载机制依赖于路径, 默认的 Lua 加载路径是_G.package.path, C 加载路径是_G.package.cpath, 支持 ? 表示文件名.

现在的做法是在程序启动或者重载的时候, 通过一个 Lua 的初始化函数去做这些设置.

之前的项目会在每个路径下写一个 file_list, 包括了依赖的目录和文件, 然后自动读取加载. 不是很喜欢这种方式, 这不是什么复杂的问题.

Lua 的模块化

Lua 的模块化有点类似 java 的 package 或者 C 的 lib, 使开发更清晰.

早期的 Lua 模块化, 是通过 module("...", package.seeall) 来显式声明的, 调用者 reqiure 文件名即可. 之所以需要 package.seeall, 大概是将 module 写入 _G 表中, 因此也带来了全局表污染的问题.

Lua5.2 之后已经不推荐这种方式了, 更好的做法是下面这样:

-- file: mod.lua
local mod = {}
function mod.hello()
    print("hello, module")
end
return mod
-- file: test.lua
local M = require "mod.lua"
M.hello()

Lua 的热更新

在 Lua 中, 一般使用 loadfile 或者 require 来加载模块, 区别在于 require 是按需加载, 如果已经加载过则不会重复加载.

在使用 require 时做热更, 需要把之前加载的内容置为 nil, 即 _G.package.loaded 表中的 module 项设置为 nil, 再重新 require.

热更新最好做到按需加载, 否则在脚本量比较大的时候, 瞬间的重新加载会带来 CPU 的波峰, 导致游戏瞬卡.

除此之外, 可能还有一些全局数据不能重置, 这里的观点是数据和逻辑分开, 数据做持久化, 尽量不要放到脚本层, 从根源上避免这个问题.

C 调用 Lua

C 与 Lua 通信的关键在于一个虚拟的栈, 几乎所有的 Lua C API 都是在对这个虚拟栈在操作. 这个虚拟栈解决了两个困难的问题:

  • Lua 是一个有自动垃圾回收的语言, 而 C 没有, 两者矛盾. 而虚拟栈是由 Lua 管理的, 所以 Lua gc 时知道栈上的哪个值被 C 使用, 哪些是可以回收的.
  • Lua 的动态类型, 与 C 的静态类型, 不一致, 做类型映射比较困难. 通过 lua_toXXX() 接口可以把栈上的 Lua 数据转为 C 可以使用的数据.

Lua调用C

Lua 调用 C 是动态链接的思想, 需要在 C 中注册函数, 即, 需要把 C 函数的地址以一个适当的方式传递给 Lua 虚拟机, 常见的是编译一个 dll 或者 so, 给 Lua require.

一个 Lua 的 C 库, 除了定义了一系列的 C 函数, 还必须定一个和 Lua 库的主 chunk 通信的特殊函数. 一个简单示例如下:

// 函数必然是lua_CFunction类型
// in: string log-content}
// out:
static int l_log(lua_State* L)
{
    ...
    return 0;
}

...

// 需要luaopen_XX格式
// XX最好就是编译成so的name, 在lua中直接require就能调用之
int luaopen_lpolar(lua_State* L)
{
    luaL_Reg reg[] = {
        {"log", l_log},
        {NULL, NULL},
    };

    luaL_newlib(L, reg);
    return 1;
}

Lua 的 debug 日志

现在 Lua 中的日志接口是封装在 C Lib 中的, 代码如下, 其中最重要的是通过 lua_getstack 获取当前 Lua 栈上的数据信息, 例如行号和文件名, 并打印出来.

日志能非常好的辅助调试, 但是这种方式的开销会比较大, 在线上的正式环境中不建议使用.

// in: string log-content
// out:
static int l_log(lua_State* L)
{
    lua_Debug ar;
    lua_getstack(L, 1, &ar);
    lua_getinfo(L, "Sl", &ar);
    const char* content = luaL_checkstring(L, 1);
    WLOGLUA("[%s:%d][LUA] %s", ar.source, ar.currentline, content);
    return 0;
}

参考文章

  1. 《Programming In Lua》
  2. 《Lua 模块化开发》
  3. 《如何实现 Lua 代码的热更新》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一名叫大蕉的程序员

合格的配置中心应有的素养No.76

最近在看配置中心的一些设计,好像基本都是五花八门,主要看的是还是携程的 Apollo 这个开源的配置中心项目。一直以来都觉得配置中心很重要,因为这对于灰度发布,...

19080
来自专栏沈唁志

WordPress有新评论微信提醒管理员

25140
来自专栏Spark学习技巧

HBase最佳实践-读性能优化策略

就职于网易杭州研究院后台技术中心数据库技术组,从事HBase开发、运维,对HBase相关技术有浓厚的兴趣。

58550
来自专栏北京马哥教育

corosync+pacemaker高可用集群

简介 高可用集群,是指以减少服务中断(如因服务器宕机等引起的服务中断)时间为目的的服务器集群技术。简单的说,集群就是一组计算机,它们作为一个整体向用户提供一组网...

353120
来自专栏北京马哥教育

corosync+pacemaker高可用集群

一、简介 高可用集群,是指以减少服务中断(如因服务器宕机等引起的服务中断)时间为目的的服务器集群技术。简单的说,集群就是一组计算机,它们作为一个整体向用户提供一...

685150
来自专栏技术点滴

git使用小结

git使用小结 很多人可能和我一样,起初对git是一无所知的。我也是因为一次偶然的机会接触到git,并被它强大的功能所蛰伏。git其实就是一种版本控制工具,就像...

21680
来自专栏生信技能树

用wget下载需要用户名和密码认证的网站或者ftp服务器文件

虽然我以前经常写爬虫,但毕竟是代码活,复用性非常低,每次得耗十几分钟解析网页并且写好代码。而熟悉linux的朋友都应该了解wget这个神器,有了url之后一行命...

2.5K80
来自专栏菜鸟致敬

[菜鸟致敬⑤] 极简搭建 hexo博客

可能有人看到这里觉得文章写得太省略,比如 github还需要添加 ssh密匙一类的旁枝末节的东西,但是我想说的是,文章适用人群是菜鸟程序员而不是懵逼小白,我们需...

11230
来自专栏编程

左手用R右手Python系列——使用多进程进行任务处理

数据抓取中的密集任务处理,往往会涉及到性能瓶颈,这时候如果能有多进程的工具来进行支持,那么往往效率会提升很多。 今天这一篇分享在R语言、Python中使用调用多...

22780
来自专栏美团技术团队

【技术博客】Cache应用中的服务过载案例研究

简单地说,过载是外部请求对系统的访问量突然激增,造成请求堆积,服务不可用,最终导致系统崩溃。本文主要分析引入Cache可能造成的服务过载,并讨论相关的预防、恢复...

35150

扫码关注云+社区

领取腾讯云代金券