Lua配置表存储优化方案

简介:

◆◆
简单介绍

Lua作为一个极为精简的嵌入型脚本语言,已经广泛地用在了游戏业。Lua的存在一般是两种场合,一种用于实现游戏上层业务逻辑,一种则利用了Lua语言本身灵活简单的数据表达能力而被广大程序员用于数据的存储,也就是常说的配置文件。

一般来说配置文件的初始来源是策划维护着的、有着一定格式约束的Excel表格,经由程序员提供的导出工具,把Excel的表格数据导出成为游戏能直接读取使用的Lua源码。

这些源码文件以Lua table的形式存储与Excel等价的数据,通常可以简单把这个配置表看成是一组2维数组,转换成配置就是一个key(Excel第一列)对应一组子数据(Excel中一行),那么整个配置数据就是一个大表包含着若干小表,如下:

原始配置表:
请输入图片描述

转换成Lua后大致是这样:

local mapsetting = 
{
    [1] = {
        name = "map1",
        useminimap = "map1000",
        show_name = "十里桃林",
        cam_pitch = 40,
        cam_yaw = 230,
        cam_dis = 10,
        pkflag_support = 1,
        pkflag_punish = 1,
        maxnum = 50,
        scheduleConf = {},
        scriptId = {},
        ...
    },
    [2] = {
        name = "map2",
        useminimap = "map1000",
        show_name = "碧波潭",
        cam_pitch = 40,
        cam_yaw = 230,
        cam_dis = 10,
        pkflag_support = 1,
        pkflag_punish = 1,
        maxnum = 50,
        scheduleConf = {},
        scriptId = {10002},
        ...
    },
    [3] = {
        name = "map3",
        useminimap = "map1000",
        show_name = "梵音谷",
        cam_pitch = 40,
        cam_yaw = 230,
        cam_dis = 10,
        pkflag_support = 1,
        pkflag_punish = 1,
        maxnum = 50,
        scheduleConf = {},
        scriptId = {},
        ...
    }
    ...
}
return mapsetting

如果配置文件中的数据过大,或着是有冗余的无用数据,那么势必会导致输出的Lua文件过大,这将严重影响加载速度和增大内存占用量。本文将介绍一种消除配置文件中冗余数据,达到压缩和优化数据存储的方法。


◆◆
优化思路

介绍该方法前,大家先来看看一般数据在哪些地方会有冗余。由上面的示例图我们可以清晰地看到数据的冗余点:

  1. 大量的数据是重复的,或着是代表没有意义的空值(比如0,[]等);
  2. 大量的中文字符串是需要游戏后期做本地化处理的;
  3. 很多复合型数据(子表,数组)内容是一样的;

搞清楚了数据冗余的原因,我们就可以制定优化方案:

  1. 对于Excel中的一列,出现次数最多的值认定为默认值,然后把它从Lua表中剔除掉,然后利用metatable机制实现全局默认值存储;
  2. 对于中文字符串,替换为一个唯一的id标识,写回到Lua表中,读取的时候加上相应的查找替换;
  3. 对于一些复杂的子表或着数组,做唯一化替换处理,替换后写回到原始数据中;

能看出来,上面的操作其实做的都是唯一化处理,所以有个要求就是整个Lua表的数据必须是只读的,如果不满足以上条件,一切优化都是错误的。所以在最后我们需要把整个表改为只读,以保护数据不被错误篡改。

优化后变成了下面这样:

local __rt_1 = {
    0
}
local __rt_2 = {
}
local __rt_3 = {
    10004
}
...
local __rt_34 = {
    desc = "@40313",
    drop_show = __rt_33,
    scriptId = {
        10001,
        10011
    },
    shield = 6,
    show_name = "@470882",
    subtype = 4
}
...
--超出Lua局部变量个数,则需要另外单独存储和引用
--createtable是把c中创建表的函数导入到lua中,这样通过创建一个预分配
--的表,能有效的避免table re-hash,有效利用内存
--下面创建一个预分配919个数组array-part、0个元素的空hash-part
local __rt = createtable and createtable( 919, 0 ) or {}
__rt[1] = {
    2861,
    1,
    46,
    8
}
__rt[2] = {
    2861,
    1,
    55,
    12
}
__rt[3] = {
    2861,
    1,
    50,
    7
}
...
local mapsetting = {
    {
        name = "map1",
        scriptId = __rt_2,
        shield = 0,
        show_name = "@384651",
        type = 0,
        ...
    },
    {
        desc = "@424100",
        music = "map2_r",
        name = "map2",
        scriptId = {
            10002
        },
        shield = 0,
        show_name = "@424100",
        ...
    },
    {
        bloomtype = 1,
        scriptId = __rt_2,
        shield = 0,
        show_name = "@491800",
        type = 0,
        ...
    },
    {
        cam_yaw = 1,
        scriptId = __rt_2,
        shield = 0,
        show_name = "@499116",
        type = 0,
        ...
    }
}
...
--大量的默认值存储在这里
local __default_values = {
    Belongto = 0,
    Instance_Show = 0,
    Instance_group = 0,
    daily_getcount = 9,
    damage_modify = 1,
    desc = "@282313",
    difficult = 0,
    pkflag_support = 1,
    scheduleConf = __rt_2,
    scriptId = __rt_32,
    ...
}
...
do
    local base = {
        __index = __default_values, --基类,默认值存取
        __newindex = function()
            --禁止写入新的键值
            error( "Attempt to modify read-only table" )
        end
    }
    for k, v in pairs( mapsetting ) do
        setmetatable( v, base )
    end
    base.__metatable = false --不让外面获取到元表,防止被无意修改
end

return mapsetting

看上去可读性没有原始的那么高了,一些被多处引用的子表已经被替换成了一个变量,这些变量是以local的形式存储在作用域的。由于Lua本身的一些限制,一个作用域内能够存放的最大local变量的个数是200个(lparser.c #define MAXVARS 200),所以超过个数限制的多余部分表会被放入一个临时数组中,初始化的时候需要额外的查表,稍微多一些开销,但可以接受。


◆◆◆
归纳总结

一般来说配置表文件优化后只有之前不到一半的大小,大量的重复数据被优化掉了,极大地提升了加载时间和内存占用。

前后文件对比如下:
请输入图片描述
最后说下宿主语言环境中,如果需要读取Lua表中的子表数据,我们可以把该表的pointer拿出来作为键值,存放于查找表中,这样宿主环境中也能读取到一个唯一的数组,节约内存。





原文出处:侑虎科技
本文作者:admin
转载请与作者联系,同时请务必标明文章原始出处和原文链接及本声明。

目录
相关文章
|
8月前
|
应用服务中间件 API 调度
Kruise Rollout:基于 Lua 脚本的可扩展流量调度方案
Kruise Rollout:基于 Lua 脚本的可扩展流量调度方案
|
10月前
|
运维 5G Go
Go或者C中调用Lua业务脚本,实现终端应用的热更新方案
Go或者C中调用Lua业务脚本,实现终端应用的热更新方案
|
11月前
Lua笔记表实现class
Lua笔记表实现class
38 0
|
XML Java 数据格式
【Lua基础 第4章】Lua的流程控制、#的作用、table的创建方式、table表常用方法、函数、多返回值、可变长参数
Lua的流程控制、#的作用、table的创建方式、table表常用方法、函数、多返回值、可变长参数
117 0
【Lua基础 第4章】Lua的流程控制、#的作用、table的创建方式、table表常用方法、函数、多返回值、可变长参数
Lua Table表实现字典
Lua Table表实现字典
675 0
Lua Table表实现字典
|
人工智能 索引
lua表排序
对于lua的table排序问题,一般的按照value值来排序,使用table.sort( needSortTable , func)即可(可以根据自己的需要重写func,否则会根据默认来:默认的情形之下,如果表内既有string,number类型,则会因为两个类型直接compare而出错,所以需要自...
838 0
lua实现深度拷贝table表
lua当变量作为函数的参数进行传递时,类似的也是boolean,string,number类型的变量进行值传递。而table,function,userdata类型的变量进行引用传递。故而当table进行赋值操作之时,table A 赋值给table B,对表B中元素进行操作自然也会对A产生影响,当然对B表本身进行处理例如B =nil或者将表B指向另一个表,则对A是没什么影响的;下面即是对lua table的深度拷贝。
1440 0