lua动态链接库(luaopen_*函数的使用)

Posted by 涵曦 at 2014-01-07 with tags lua, 代码阅读, Linux

lua中使用c动态库,像luacjson(支持unicode),luasocket,都是以动态链接库的形式在lua中使用的,至于怎么写这些动态链接库很少有教程说到,下面我就说说如何把c文件编译成动态库。

首先,假设需要在lua中调用一个在c中实现的求和函数,函数名add(a,b)。

我给这个测试库取名为dylib,它包含一个函数add。lua中这样使用:

    local dylib = require "dylib.test"
    local c = dylib.add(1,2)
    print(c)

上面的dylib.test就是我编译生成的dylib/test.so文件。这个文件该怎么生成?如下:

    int
    luaopen_dylib_test(lua_State* L) {
        luaL_Reg l[] = {
           { "add", *dylib_add* },
           { NULL, NULL },
        };
        luaL_checkversion(L);
        luaL_newlib(L,l);

        return 1;
    }

这个函数名有个命名规则,前缀为luaopen,后面就是lua中require的字符串(将'.'转换成'')。当执行到require "dylib.test"时,lua解析器会去dylib/test.so文件中寻找并执行函数名为luaopen_dylib_test的函数。找不到则报错:

    lua: error loading module 'dylib.test' from file './dylib/test.so':
        ./dylib/test.so: undefined symbol: luaopen_dylib_test
    stack traceback:
        [C]: in ?
        [C]: in function 'require'
        test.lua:1: in main chunk
        [C]: in ?

注意到dylib_add就是就是要实现的dylib.add函数。现在实现它:

    int
    dylib_add(lua_State* L) {
        int a = lua_tonumber(L,1);
        int b = lua_tonumber(L,2);
        int c = a+b;
        lua_pop(L,2);
        lua_pushnumber(L,c);
        return 1;
    }

这函数就是把两参数加起来,然后返回和。最后编译生成so文件:

    gcc -g -Wall --shared -fPIC -o dylib/test.so dylib_test.c

注意要给它建一个文件夹dylib。因为require的时候会把"dylib.test"转成"dylib/test"默认去该路径下寻找so或者lua文件。当然,你修改了搜索路径那是另外一回事了。

基本的就是这样子了。正在看云风的hive游戏服务器框架(skynet的精简版,不是apache hive)。

下一篇会讲到luaL_requiref函数,实现一个动态库so文件中包含多个库。因为直接用两个luaopen函数是有问题的,一般一个luaopen函数对应一个so文件。

Buck快速入门

Posted by 涵曦 at 2013-12-26 with tags 翻译 , 杂文

原文地址:http://facebook.github.io/buck/setup/quick_start.html

注意:Buck仅支持Mac OS X和Linux,不支持Windows。

这是一份使用Back的快速入门手册。为了更容易开始使用,Back提供了buck quickstart命令创建一个简单的Android工程,它是已配置的且使用Buck就可开箱即用的。

如果你不习惯使用命令来生成一个项目,那么你可以通过这个快速入门指南提供的一步一步的指示来创建您的第一个项目。这个项目中的每个文件的用途都将在这里介绍。

第1步:安装Buck

因为这是一个快速入门说明书,我们假定你的电脑上已经安装好Ant和Android SDK,如果你没安装它们,请移步到下载和安装Back

执行下面的命令从GitHub中checkout Back。编译,添加Back到$PATH:

    git clone git@github.com:facebook/buck.git
    cd buck
    ant
    sudo ln -s ${PWD}/bin/buck /usr/bin/buck

同样的,你也可能想设定buckd

    sudo ln -s ${PWD}/bin/buckd /usr/bin/buckd

当前没有办法下载预编译好的Buck二进制文件。

第2步:执行"buck quickstart"

现在你已经安装好Buck了,我们来使用它编译一个简单的Android应用程序。

我们需要在一个空的目录下开始,所以创建一个新文件夹,进入到新建的文件夹,执行命令:

    buck quickstart

你将看到下面的提示,输入你安装好的Android SDK的本地路径。如果你的Android SDK安装在~/android-sdk-macosx,那么你将会看到下面的提示:

    Enter the directory where you would like to create the project: .
    Enter your Android SDK's location: ~/android-sdk-macosx

作为选择,你可以指定参数中输入SDK的路径来跳过提示。

    buck quickstart --dest-dir . --android-sdk ~/android-sdk-macosx

看到下面的输入说明你的命令成功了:

    Thanks for installing Buck!
    
    In this quickstart project, the file apps/myapp/BUCK defines the build rules. 
    
    At this point, you should move into the project directory and try running:
    
        buck build //apps/myapp:app
    
    or:
    
        buck build app
    
    See .buckconfig for a full list of aliases.
    
    If you have an Android device connected to your computer, you can also try:
    
        buck install app
    
    This information is located in the file README.md if you need to access it
    later.

第3步:编译和安装你的App

上面操作正确之后,你可以执行下面的命令编译你的程序:

    buck build app

如果你开启了Android模拟器或者有设备通过usb连接(你可以执行adb devices验证一下),然后你可以安装,运行这个app:

    buck install app 

这个app的名字是一个生成目标的别名,编译规则的一个标识,作为编译你的app。你可以.buckconfig文件中(在你工程的根目录)修改它.

第4步:修改App

现在你知道如何使用Buck生成APK了,你大概想要修改这个示例代码来编译你想要的app。Java代码在java/目录下,建议使用目录结构和包结构相匹配,这是Java的一贯风格。

比较起来,Android 资源需要放在res/目录下,你可以多个Java包在java/目录下,你同样可以拥有多个android_resourceres/目录下。这样就更容易在需要的时候处理Android资源目录和Java代码之间的映射。

现在你应该从你需要的一切开始,如果你的项目变得越来越复杂而且你想利用Buck的更多先进但不复杂的共,你将会从头开始探索更多的Buck的文档。

当前,使用buck生成的IntelliJ工程导入到IntelliJ不是那么的方便,查看answers to our questions会有所帮助。

一年工作总结

Posted by 涵曦 at 2013-11-17 with tags 总结, 杂文

工作已经满一年了。。。记得还是去年十月多的时候来广州找工作的,找了大概一个星期左右,然后找到这家公司后就一直待到现在。今年毕业的时候回了趟学校,到现在工作时间大概有一年了。第一个手游项目已经上线有一段时间了,还不知道盈利情况怎么样,又开始了一个新的卡牌游戏。工作一直挺充实的,第一个项目的从零开始我就加入了项目组,从头至尾参与了项目的研发,感觉收获还是有点的,但一时又想不到怎么说。现在新的卡牌游戏用的是上一个项目的框架。既然不知道总结什么,我就把游戏服务端框架说一说吧。

Sanjose

对着上面的框架图来说,首先是网关FGGateway,作为一个连接服务器,处理服务端和客户端的连接,对应的数据分配。我公司是用libevent实现的。代码量很少,方便移植。

逻辑服务器FGServer和FGClient所使用的网络使用select实现的,同一套代码,从网络的角度这逻辑服务器也是一个客户端。

FGClient连接FGServer的过程图上没说到FGGateway的中转过程,这里简单说下流程:

  • FGServer

    1. FGServer 调用 connect 连接FGGateway,
    2. 连接成功,FGServer发送第一条协议给FGGateway,携带设备信息告诉FGGateway说自己是服务端。
    3. FGGateway 验证设备信息,成功则把FGServer加到在线服务器列表中。
  • FGClient

    1. FGClient 调用 connect 连接FGGateway,
    2. 连接成功,FGClient发送第一条协议给FGGateway,携带设备信息告诉FGGateway是说自己是客户端。
    3. FGGateway 把FGClient保存到客户端队列,
    4. FGClient 发送第二条协议获取在线服务器列表,FGGateway返回在线服务器列表。
    5. FGClient 发送第三条协议:选服协议,FGGateway把FGClient和FGServer绑定。
    6. FGClient 接下来发送协议都会直接转发到FGServer。FGServer发送协议也会根据客户端的fd直接转发到FGClient。

FGServer和FGShmDB的交互,FGShmDB是用共享内存实现的内存数据库,只是简单的实现数据库的数据加载到内存,不支持数据从内存中移除,只是将用到的数据库中的数据按需加载到内存中,这样一来读取数据的速度会加快(第一次是从数据库中加载出来放到内存中,之后都是直接操作内存)。然后FGServer就是操作FGShmDB分享出来的内存。

FGLogin 是比较简单的一个http服务。主要是接收客户端的url请求,返回服务器列表给客户端。用于集合服务器。现在是直接用libevent的http实现的,用lua包装了一下。主要实现了用户注册,修改密码,用户登录,获取服务器列表等操作。其实可以用php实现它的,不知为啥会选用c。

使用cocos2dx的lua脚本写游戏逻辑

Posted by 涵曦 at 2013-10-06 with tags linux, cocos2dx

脚本代码文件结构

luaScript
├── conf.lua
├── global.lua
├── include.lua
├── logic
│   ├── HXGameBoardLogic.lua
│   ├── HXGameIcon.lua
│   ├── HXGameScene.lua
│   ├── HXMainMenuScene.lua
│   └── logic.lua
├── main.lua
└── util
    ├── AudioEngine.lua
    ├── HXUtil.lua
    └── util.lua

项目地址: 使用cocos2dx引擎为基础完成一个手机游戏的基本框架

游戏资源和逻辑脚本来自crosslife, 真应该感谢他开源了这套脚本逻辑. 国庆这几天都在修改这套代码, 收获还挺多的.

游戏逻辑分类

因为游戏比较小,所以把所有的逻辑都放到了logic文件夹下面,其中HXGameBoardLogic.lua文件用来实现与游戏数据结构相关的逻辑,比如坐标点和图标位置转换,检测某个图标是否可以被消除等等跟图形界面没关的都放这里了.

HXGameIcon.lua用来读取图标.

HXMainMenuScene.lua用来实现开始菜单界面.

HXGameScene.lua就是游戏的主场景了.包含创建场景,各种游戏操作逻辑能看得到的都在这里实现了.

游戏数据结构

  • 二维数组GameBoard是一个7X7的用于存放每个格子图标的index
  • 没有单独建立表来存放每个node的引用,而是直接使用scene场景来管理每一个格子图标的精灵,并使用tag来索引它,由于是7X7的,所以就使用二位数来表示横纵位置.如果格子定在10X10以上那得修改代码了,没有做兼容.

主要的算法

  • 判断P(x,y)能否被消除: x,y为1到7的数字.算法1是像扫雷游戏那样判断,搜集周围节点状态一起判断,3个连在一起就可消除的就需要判断周围横纵8个节点的状态.我用的是另外一种算法2:从P点开始搜索,先横向搜索左右两边的节点状态,如果左边相同左边继续搜寻,不相同则停止左边的搜索,右边的节点和左边搜索过程是一样的,所以我就在一个循环里头完成这两个操作的判断.比算法1有个好处就是可以在判断完后得到可消除的节点.而算法1还需要重新搜索可一消除的节点数.
  • 检测棋盘有无可移动消除棋子:用的是全部遍历测试方法,逐个节点左右上下互换位置后判断能否被消除来检测.这算法效率很定是不行的,太耗时间了,打算优化的,还没有想到更好的方法.

节点下落填充

(思想来自一个csdn的人写的,忘记在哪了)

  • 根据需要消除的节点,计算出每一列里有多少个需要消除的节点,计算的时候从下网上开始计算,可以得出每个节点下面可以有几个被消除的节点.根据这个就可算出它需要下移的位置了,然后就可以调用moveto了.
  • 为每列生成新节点,根据每列需要消除的节点数生成新节点,和计算出掉落位置,然后掉用moveto.
  • 在最后一个节点掉落完成之后弄个回调函数,完成掉下来的节点(前两步做好记录)能否被消除的检测.
  • 在检测函数最后面判断是否还有节点在往下掉,如果没有往下掉的格子了,再检测现在的棋盘能否可以有棋子移动,不可以移动就掉落闪烁节点.

掉落动画保护处理

为了玩家看到的动画效果是完整的,我屏蔽了掉落动画时的屏幕touch事件.如果不这样做的画会出现棋子掉落位置出错的情况.还有一种解决办法是立即结束动画(但是需要立即完成moveto事件.要不然棋子就不在目标位置了.),但对cocos2dx不熟,导致没有成功实现这种方法.

游戏后续补充

现在这个游戏只是能玩,有个分数在那里显示着,但还不像个游戏.后面我给他添加闯关模式,无尽模式什么的...在玩法上添加点东西,比如最高记录保存.

顺便添加一点apk打包知识

无需安装eclipse,只需要ant就足矣.

  • 生成keystore文件
keytool -genkey -alias hanxigame.keystore -keyalg RSA -keystore hanxigame.keystore
  • 修改ant.properties文件
key.store=./hanxigame.keystore
key.alias=hanxigame.keystore
key.store.password=hanxigame
key.alias.password=hanxigame
  • 执行ant release

  • 如果出现环境变量问题,需要把一些必要的环境变量设置好.ant需要用到NDK_ROOT这个变量

#set android environment
export ANDROID_SDK_ROOT=/home/hanxi/Lib/android-sdk
export NDK_ROOT=/home/hanxi/Lib/android-ndk
export COCOS2DX_ROOT=/home/hanxi/Lib/cocos2d-x
export PATH=$PATH:$ANDROID_SDK_ROOT:$ANDROID_SDK_ROOT/tools
export PATH=$PATH:$NDK_ROOT
  • 这个游戏第一版就这样子结束吧,编译一个release包放到百度网盘里面了.可以下载玩玩的.自适应屏幕的(具体怎么适应可去网上找资料,或者直接阅读我的代码). 安卓安装包下载地址

使用cocos2dx搭建手游基本框架

Posted by 涵曦 at 2013-09-30 with tags linux, cocos2dx

工程源码结构

HXGame
├── Classes
├── HXModules
│   ├── HXEngine
│   │   ├── HXEngine.cpp
│   │   └── HXEngine.h
│   ├── HXLuaModules.cpp
│   ├── HXLuaModules.h
│   ├── HXModules.h
│   ├── proj.android
│   │   └── Android.mk
│   └── proj.linux
│       ├── Makefile
│       ├── modules.mk
│       └── obj
├── lib
├── libs
│   ├── cocos2dx
│   ├── CocosDenshion
│   ├── extensions
│   ├── external
│   ├── Makefile
│   └── scripting
│       └── lua
├── LICENSE
├── proj.android
├── proj.ios
├── proj.linux
├── proj.mac
├── proj.win32
├── README.md
├── Resources
│   ├── audio
│   ├── fonts
│   ├── image
│   └── luaScript
│       ├── conf.lua
│       ├── include.lua
│       ├── main.lua
│       └── util
│           ├── HXUtil.lua
│           └── util.lua
└── tools
    └── tolua++
        ├── basic.lua
        ├── build.sh
        ├── Cocos2d.pkg
        ├── HXModules.lua
        ├── HXModules.pkg
        └── tolua++.bin

项目地址: 使用cocos2dx引擎为基础完成一个手机游戏的基本框架

HXModules文件夹自己主要需要完成的模块,包括手游中需要完成的通用代码都在此以模块的方式实现.其中HXLuaModules.cpp是tolua++生成的文件. 现在只实现了一个功能,执行lua脚本和重启lua脚本(在lua中可以重启lua脚本).

libs文件夹是从cocos2dx工程拷贝过来的,减去了不需要的文件,里面的工程文件也做了修改(现在完成了安卓工程和linux的编译和运行).

lib文件夹是用来放libs和HXMoules生成的lib文件(linux 工程下).

proj文件夹是从cocos2dx拷贝过来的,修改了安卓工程和linux工程.完美编译运行.

Resources文件夹就是资源和脚本的存放处了.lua脚本目录格式也是有规则的.每个lua模块一个文件夹,并在include中包含他们.

tools文件夹中的tolua++用来生成luabing文件...

这样设计目录结构的目的是为了方便工程的扩展,而不是所有文件都放在Classes文件夹.把通用的代码可以放到Modules文件夹,程序逻辑放在lua中实现.C++只实现一些通用模块.可能在Modules文件下还要添加platform文件夹用来存放和平台相关的代码,比如需要添加一个微博分享模块,那就得用java实现安卓相关的代码,用objc实现ios相关代码,然后提供公共的C++接口给lua.

框架设计来源

架构思想来自关中刀客

Sanjose

[原创]C++指向成员函数的函数指针随想

Posted by 涵曦 at 2013-09-01 with tags C++, 函数指针, 成员函数

指向成员函数的函数指针

首先看一个指向成员函数的函数指针的例子,比较常用,代码如下:

    class A {
    public:
        void func() {
            cout << "func" << endl;
        }
    };
    int main(int argc, char * argv[]) {
        void (A::pf)() = &A::func;
        A a;
        (a.*pf)();
        return 0;
    }

可以看到,定义指向类A的成员函数A::func()的函数指针定义pf的格式必须带上A::,给pf赋值不能使用对象a的func赋值,而必须使用A::,且必须加上&(普通的c函数是可以不加&的).
使用&a.func对pf赋值时,我使用g++编译,给出的错误提示很明确易懂:
错误: ISO C++ 不允许通过取已绑定的成员函数的地址来构造成员函数指针。请改用‘&A::funcB’ [-fpermissive]

ps: 网上有人说clang给出的提示更好,但现在测试出来clang给出的提示很模糊:
error: cannot create a non-constant pointer to member function
p = &a.funcB;
^~~~~~~~
g++给出错误位置是多少行多少列,clang则是用波浪线标出来的.

类成员为函数指针

    class A {
    public:
        void (*pq)();
    };
    void func() {
        cout << "func " << endl;
    }
    int main(int argc, char * argv[]) {
        A a;
        a.pq = func;
        a.pq();
        return 0;
    }

这个例子也验证了第一个例子所说的,c函数指针赋值是不用加&的. 这个例子学习C语言的时候就见过了,写出来只是为了过度到第三个更加复杂例子的.

类成员函数指针指向类成员

    class A {
    public:
        void func() {
            cout << "func " << endl;
        }
        void (A::*p)();  //  必须加A::
        A () { // 更加但痛的调用方式
            p = &A::func; // 必须加A::
            (this->*p)();
        }
    };
    int main(int argc, char * argv[]) {
        A a;
        a.p = &A::func;
        (a.*(a.p))(); // 和第一个例子有点像,使用a.p替换pf就是了.
        return 0;
    }

首先是函数指针的定义,指向自己所在类的成员函数,定义的时候必须加A::前缀.然后在给p赋值的时候必须和第一个例子一样加A::(成员函数内部使用也不例外).然后就是使用p的时候.在成员函数内部使用(this->p)();.在A的对象中使用(a.(a.p))();的方式.

函数指针的作用就是为了实现多态吧.下面是我的全部测试代码.在gcc4.7,clang3.0测试通过.没在网上找啥资料,全靠编译器的错误提示完成这段代码,错误之处还望读者提出来.