Lua API
这里列出主要的调用,每一个都能在当前版本的 evolve 中使用。
start
1脚本是 scripts 文件夹里的一个 .lua 文件:在游戏菜单中,“设置 › 脚本 › 打开文件夹”会打开这个文件夹。脚本返回一个回调表,保存后会重新读取。Lua 5.4,所有脚本共用一个 VM,回调逐个执行。
- 1在 on_draw 里绘制,在 on_update 里执行游戏操作。renderer.* 在帧外什么也画不出来,orders.* 在 on_draw 里调用会走错误的路径。
- 2从 entities.* 取到的实体只在当前帧有效:先检查 e:valid(),列表本身也要重新从 entities.* 获取。
- 3启动后的前 ~10 秒在进行 schema 遍历。位置、队伍和生命值可以立即读取;护甲、技能和 gamerules 在遍历结束前都返回 0。
- 4client.local_hero() 为空时,所有按队伍的过滤都会把所有人当作敌人:先拿到英雄,再选目标。
- 5坐标使用 Source 世界单位:地图在 X 和 Y 上大约从 -8288 到 8288,一个点就是 vec(x, y, z)。
client.local_hero()→ Entity|nil本地玩家的英雄。进入比赛前为 nil。
client.screen_size()→ number, number窗口的宽度和高度,单位为像素。
client.cursor_world()→ Vector|nil光标下方的点,位于英雄所在的 z 平面上。
globals.curtime()→ number游戏时间,单位为秒。所有计时器都用这个时钟。
globals.tickcount()→ number客户端真实的 tick,30 Hz,单调递增。
注意游戏时间暂停时仍会继续计数。
vec(x, y, z)→ Vector一个点或一个方向。相关的还有 vec2、angle 和 color(r, g, b, a),分量取值 0..255。
log.info(msg)向 evolve.log 写一行。脚本没有反应时,首先用它排查。
-- scripts/hello.lua
local s = {}
function s.on_update() -- game logic
local me = client.local_hero()
if not (me and me:valid()) then return end
end
function s.on_draw() -- renderer.* only
local me = client.local_hero()
if not (me and me:valid()) then return end
local x, y, on = renderer.world_to_screen(me:position())
if on then
renderer.text(font_default, x, y - 40, color(255, 255, 255, 255), "hello")
end
end
return spacks
2脚本包就是一个文件夹:pack.json、入口、模块、图片和数据。同一个文件夹放进 scripts/ 就能运行,上传到商店时也是把它压缩成 zip(作者后台 › 版本)。
- 1pack.json:version 必填(格式如 1.2.0),entry 是启动文件(默认 main.lua),name 是脚本列表中的名称,evolve 是它适配的版本。
- 2文件夹名就是脚本包名。模块从它开始加载:require("my_pack.lib.util") 读取 lib/util.lua。在商店中,文件夹以作品的 slug 命名。
- 3以 ./ 开头的路径指向你的脚本包文件夹:renderer.load_image("./art/icon.png")、菜单图标 "./art/…"、fs.read("./data/x.json")。不需要知道文件夹名。
- 4脚本包里可以放 .lua、图片 .png .jpg .bmp .tga .gif(每边最大 4096 px)和数据 .json .txt .md .csv。zip 最大 16 MB,最多 1500 个文件。
- 5上传时每个 .lua 都会被编译,每张图片和每个 json 都会被打开。回复会一次列出所有问题,带文件和行号,之后由管理员审阅版本。
renderer.load_image(path)→ number按路径加载图片:"./art/x.png" 来自你自己的脚本包,"panorama/images/…" 来自游戏。返回句柄或 -1;按路径缓存。
注意在脚本加载时加载一次,不要每帧加载。脚本重新加载后,修改过的文件会自动生效。
renderer.load_image_bytes(bytes [, key])→ number用脚本已经拿到的字节(fs.read、http)创建图片。按 key 缓存,没有 key 时按内容缓存。
-- scripts/my_pack/pack.json : { "version": "1.0.0", "name": "My Pack" }
-- scripts/my_pack/main.lua
local util = require("my_pack.lib.util") -- scripts/my_pack/lib/util.lua
local icon = renderer.load_image("./art/icon.png")
local cfg = json.decode(fs.read("./data/defaults.json") or "{}")
local s = {}
function s.on_draw()
local me = client.local_hero()
if not (me and me:valid()) or icon < 0 then return end
local x, y, on = renderer.world_to_screen(me:position())
if on then renderer.image(icon, x - 16, y - 80, 32, 32) end
end
return sevents
3回调。来自引擎线程的一切都会先缓冲,再由帧循环统一取出处理,所以处理函数和 on_update 在同一处运行,可以调用任何 API。
- 1订阅有两种方式:从文件返回一个 { on_update = fn } 表,或者调用 events.register。两者都一直有效,直到脚本重新加载。
- 2事件数据只在回调内部有效:当场读取,不要把这张表留到以后再用。
- 3抛出错误的处理函数会被移除,并记录堆栈回溯;diag.errors() 会在客户端里显示它。
events.register(name, fn)订阅一个回调。
- namestring
- 回调名,例如 "on_update"。
- fnfunction
- 处理函数。参数取决于事件。
"on_update" fn()逻辑 tick,每帧一次。orders.* 和所有游戏逻辑都放在这里。
"on_draw" fn()帧。renderer.* 只能在这里使用。
"on_game_event" fn(name, data)任何原生事件:不能按名称单独订阅,所有事件都会送达。
- namestring
- 引擎事件名,例如 "dota_player_gained_level"。
- datatable
- 按名称给出事件的全部字段,在触发时截取快照。
注意由帧循环投递,最迟在触发后一帧送达,按触发顺序排列。
"on_key" fn(vk, down)来自 wndproc 的原始按键。
- vknumber
- Windows 虚拟键码。
- downboolean
- 按下或松开。
注意返回值会被忽略:脚本无法拦截游戏的按键。
"on_projectile" fn(p)创建了一个追踪弹道。
- ptable
- handle, source, target, ability, move_speed, is_attack, dodgeable, expire_time, max_impact_time。
注意其中的实体字段在投递时解析:在同一帧内死亡的单位会以 nil 送达。
"on_modifier_gained" fn(npc, mod)出现了一个修饰器。与之配对的是 on_modifier_lost(npc, name)。
- npcEntity
- 获得它的单位。
- modtable
- name、stacks、duration 以及修饰器的其余字段。
"on_prepare_unit_orders" fn(o)指令即将发往服务器。返回 false 可拦截它;o.synthetic 用来区分脚本发出的指令和玩家的点击。
- otable
- order, target, position, ability, unit, queue, issuer, synthetic。
注意唯一仍直接在游戏线程上执行的回调。引擎会等待 VM 2 ms,等不到就不经否决直接放行指令;这类情况可以用 events.veto_timeouts() 统计。
timers.after(sec, fn)→ number一次性计时器,返回它的 id。相关的还有 timers.every(sec, fn) 和 timers.clear(id)。
- secnumber
- 从现在起经过多少秒游戏时间。
- fnfunction
- 要执行的函数。
events.dropped()→ number因队列已满而丢失的事件数。应保持为 0。
local s = {}
function s.on_projectile(p)
local me = client.local_hero()
if not (me and me:valid()) then return end
if p.target == me and p.dodgeable then
log.info("incoming, impact in " ..
string.format("%.2f", p.max_impact_time - globals.curtime()))
end
end
return sentities
4实体列表。快照每秒刷新约 20 次,userdata 经过驻留处理,所以实体可以直接用作表的键。
- 1过滤器就是一个普通表:{ team = "enemy", alive = true, around = pos, radius = 600, class = "creep" }。class 按类名的子串匹配。
- 2team 是字符串 "enemy" 或 "ally",不是数字。
- 3地图上存活的野怪中,大约一半是营地预先生成的下一波:它们处于无敌状态,可以用 e:is_waiting_to_spawn() 过滤掉。

entities.all()→ Entity[]客户端快照中的所有实体。
entities.heroes(team)→ Entity[]英雄,可选只取一方。
- teamstring?
- "enemy"、"ally",不传则返回全部。
entities.npcs(filter)→ Entity[]按过滤器筛选的单位:小兵、幻象、召唤单位、防御塔。
- filtertable?
- team / alive / around / radius / class。
entities.in_radius(pos, radius, filter)→ Entity[]圆内的全部实体。
- posVector
- 圆心。
- radiusnumber
- 半径,单位为世界单位。
- filtertable?
- 与 npcs 相同的过滤器。
entities.closest(pos, filter)→ Entity|nil最近的单位,不包括你自己。
- posVector
- 测量的起点。
- filtertable?
- 与 npcs 相同的过滤器。
entities.by_class(name)→ Entity[]精确匹配该类及其所有派生类,而不是按名称猜测。
- namestring
- C++ 类,例如 "C_DOTA_BaseNPC_Tower"。
entities.trees()→ Entity[]树木。相关的还有 entities.runes() 和 entities.physical_items()(地上的物品)。
entities.get(index)→ Entity|nil按索引获取实体;entities.from_handle(h) 按句柄获取。
- indexnumber
- 实体索引。
local s = {}
function s.on_draw()
local me = client.local_hero()
if not (me and me:valid()) then return end
for _, h in ipairs(entities.heroes("enemy")) do
if h:valid() and h:is_alive() and not h:is_dormant() then
renderer.circle_3d(h:position(), h:attack_range(), color(255, 80, 80, 120))
end
end
end
return sentity
5实体对象。英雄、小兵、技能和物品都是同一个类型,哪些方法有意义取决于它具体是什么。
- 1先调用 e:valid()。已消失的实体会返回默认值,而不是报错。
- 2处于战争迷雾中的实体(e:is_dormant())停留在最后一次被看到时的状态:位置、生命值和物品栏都是旧数据。
- 3用 e:handle() 作为表的键:索引会被复用,而句柄还带有序列号。
e:valid()→ boolean实体仍在游戏中。
e:position()→ Vector世界坐标位置。e:origin() 是同一个调用。
e:health()→ number当前生命值。相关的还有 max_health()、health_pct()(0..100)、mana() 和 mana_pct()。
e:is_alive()→ boolean是否存活。相关的还有 is_enemy()、is_ally()、is_illusion()、is_hero() 和 is_dormant()。
e:distance(target)→ number距离,单位为世界单位。
- targetVector|Entity
- 一个点或另一个实体。
e:ideal_speed()→ number精确的当前移动速度,已计入所有修饰器:鞋子、极速、减速、光环。
注意e:move_speed() 是网络同步的基础属性,不含修饰器。计算移动要用 ideal_speed。
e:effective_bat()→ number计入修饰器后的基础攻击间隔,攻击时间线就是据此计算的。相关的还有 e:aps() 和 e:seconds_per_attack()。
e:is_stunned()→ boolean单位状态位。is_silenced、is_rooted、is_hexed、is_disarmed、is_magic_immune 和 is_invulnerable 的读取方式相同。
注意状态位只回答是或否,不带时间。还剩多久要从修饰器得出,而不是从状态位。
e:modifiers()→ table[]所有修饰器:name、class、stacks、duration、creation_time、die_time、ability、caster、parent、is_aura。
注意parent 是身上带着这个状态的单位;对减益状态来说,它不是施法者。
e:has_modifier(name)→ boolean快速检查单个修饰器;e:get_modifier(name) 返回它的表。
- namestring
- 内部名称,例如 "modifier_bloodseeker_thirst"。
e:handle()→ number完整的 32 位句柄:索引加序列号。可以安全地跨帧用作键。
e:visible_to_enemy()→ boolean敌方是否能看到你。对你自己的单位是可靠的。
abilities
6技能和物品也是实体,有各自的方法:冷却、魔法消耗、施法距离、施法前摇,以及背后的 KV 数据。
- 1冷却、魔法值和施法阶段只同步给拥有者。在别人的英雄上,技能看起来总是就绪:这表示“没人告诉我们”,而不是“冷却已结束”。
- 2e:get_ability(name) 找不到时返回无效实体而不是 nil,并且每次调用都会扫描快照。只解析一次,存进局部变量。
- 3技能能否穿透黑皇杖,看 ab:immunity_type() == 1,而不是按名称猜。

hero:get_ability(name)→ Entity按名称获取技能。
- namestring
- 内部名称,例如 "lion_impale"。
hero:abilities()→ Entity[]英雄的所有技能,按槽位顺序排列。
注意每次调用都会扫描快照:请缓存结果,不要在绘制循环里调用。
hero:get_item(slot)→ Entity槽位中的物品;hero:items() 返回整个物品栏。
- slotnumber
- 物品栏槽位,从零开始计数:0..5 是英雄身上的物品,之后是背包和储藏处。
ab:cooldown()→ number剩余冷却时间,单位为秒,0 表示就绪。
ab:is_ready()→ boolean等级大于零且冷却已结束。
ab:mana_cost()→ number当前等级的魔法消耗;ab:ability_level() 返回等级本身。
ab:cast_range()→ number施法距离,已计入所有加成。
ab:cast_point()→ number施法前摇,单位为秒,即从下达指令到生效之间的延迟。
ab:special(key, level)→ number技能数据中的任意数值,已计入天赋和阿哈利姆神杖。
- keystring
- AbilityValues 中的键,例如 lion_impale 的 "duration"。键名以技能数据为准,不要靠猜。
- levelnumber?
- 等级;-1 或不传表示当前等级。
注意未学习的技能按当前等级会返回 0,请显式传入等级:ab:special("duration", 0)。
ab:immunity_type()→ number|nil0 无,1 对敌方穿透,2 不穿透,3 和 4 对友方同理。
注意nil 表示“没能读取”,这和 0 不是一回事。
ab:dispellable_type()→ number|nil0 无,1 强驱散,2 基础驱散,3 否。
it:item_charges()→ number物品充能:魔瓶、魔杖、影之灵龛。技能的充能则用 ab:charges()。
local s = {}
local hex
function s.on_update()
local me = client.local_hero()
if not (me and me:valid()) then return end
hex = hex or me:get_ability("lion_voodoo")
if not (hex and hex:valid() and hex:is_ready()) then return end
if me:mana() < hex:mana_cost() then return end
for _, h in ipairs(entities.heroes("enemy")) do
if h:is_alive() and not h:is_magic_immune()
and me:distance(h) < hex:cast_range() then
orders.cast_target(hex, h)
return
end
end
end
return sorders
7真正的引擎指令,走的是和鼠标点击相同的路径。在帧内发送,放在 on_update 里。
- 1每个指令都有一个可选的末尾参数 queue:orders.move_to(pos, true) 会像按住 Shift 点击一样加入队列。
- 2来自其他线程的指令(GC 回调、网络钩子、你自己的线程)必须先缓冲,再从 on_update 发出。在其他线程调用引擎会让客户端在互斥锁上死锁。
- 3每帧发一次指令并不会更快,只是在刷指令:服务器只执行最后一条,而且指令流在录像里看得见。

orders.move_to(pos, queue)移动指令。相关的还有 attack_move(pos) 和 move_to_target(entity)。
- posVector
- 要走到的位置。
- queueboolean?
- 排入队列,而不是替换。
orders.attack(target, queue)攻击一个目标。
- targetEntity
- 要攻击的目标。
- queueboolean?
- 排入队列。
orders.cast_target(ability, target, queue)对单位施法。
- abilityEntity
- 技能或物品。
- targetEntity
- 施法目标。
- queueboolean?
- 排入队列。
orders.cast_position(ability, pos, queue)对地点施法。
- abilityEntity
- 技能或物品。
- posVector
- 施法的目标点。
- queueboolean?
- 排入队列。
orders.cast_no_target(ability, queue)无目标施法;cast_toggle(ability) 切换开关类技能。
- abilityEntity
- 技能或物品。
- queueboolean?
- 排入队列。
orders.use_item(item, target)使用物品。
- itemEntity
- 物品栏中的物品。
- targetEntity|Vector?
- 目标或地点,如果该物品需要的话。
orders.stop()停止;orders.hold() 为原地不动。
orders.unit_move_to(unit, pos, queue)给受控单位而不是你的英雄下达的指令;unit_attack 用法相同。
- unitEntity
- 执行指令的单位:召唤单位、信使、第二个米波。
- posVector
- 要走到的位置。
- queueboolean?
- 排入队列。
orders.raw(type, opts)完整的 dotaUnitOrder_t 指令集,用于具名调用覆盖不到的情况。
- typenumber
- orders.types 中的指令代码。
- optstable
- target, position, ability, unit, queue, issuer, show_effects。
renderer
8基于 d3d11 的即时模式渲染器。在 on_draw 中绘制的内容只存在一帧;世界图元也可以改为只注册一次,由 C++ 负责重新投影。
- 1颜色为 color(r, g, b, a),取值 0..255。文本绘制和测量的第一个参数是字体;全局的 font_default 已经准备好。
- 2world_to_screen 返回三个值,第三个表示“这个点到底在不在屏幕上”。不检查它,你身后的东西会被镜像画出来。
- 3字体要提前预热:图集重建前取到的度量是 47px 的后备字体,据此计算的排版会出错。
renderer.world_to_screen(pos)→ number, number, boolean投影结果:x、y 以及是否在屏幕上。
- posVector
- 世界坐标中的点。
renderer.text(font, x, y, col, str, align, shadow)在屏幕上绘制文本;renderer.measure_text(font, str) 返回它的宽度和高度。
- fontFont
- 字体;用全局的 font_default 即可。
- x, ynumber
- 屏幕坐标。
- colColor
- 颜色。
- strstring
- 文本。
- alignstring?
- 由 "c"(居中)、"r"(靠右)、"v"(垂直)组合而成。
- shadowboolean?
- 在文本下方加阴影。
renderer.load_font(name, size, weight, flags)→ Font按参数缓存的字体,重复调用是安全的。
- namestring
- 系统字体名称。
- sizenumber
- 字号,单位为像素。
- weightnumber?
- 字重。
- flagsnumber?
- 样式标志。
renderer.line(x1, y1, x2, y2, col, thick)线段。相关的还有 rect、rect_outline、rounded_rect、triangle、poly 和 gradient。
renderer.circle_filled(x, y, r, col, segs)屏幕上的实心圆;renderer.circle 绘制轮廓。
renderer.circle_3d(center, radius, col, segs, thick)地面上的圆环,和游戏自带的范围指示器一样。
- centerVector
- 世界坐标中的圆心。
- radiusnumber
- 半径,单位为世界单位。
renderer.load_image(path)→ Image图片;用 renderer.image(handle, x, y, w, h, tint) 绘制。
- pathstring
- 磁盘上的文件,或游戏自带的 panorama 资源。
renderer.world_circle(id, center, radius, col, opts)注册后由 C++ 接管的世界图形:在 tick 中注册,之后每帧重新投影。相关的还有 world_line、world_poly、world_text、world_image 和 world_clear。
- idstring
- 图形的 id:相同的字符串会替换之前的图形。
- centerVector
- 中心。
- radiusnumber
- 半径,单位为世界单位。
- colColor
- 颜色。
- optstable?
- ttl(默认 0.25 s)、thickness、segments、filled、layer。
注意注册不等于绘制:在 on_update 中调用即可,它会显示在 on_draw 所画内容的下层。
local s = {}
local font = renderer.load_font("Verdana", 13)
function s.on_draw()
local me = client.local_hero()
if not (me and me:valid()) then return end
for _, h in ipairs(entities.heroes("enemy")) do
if h:is_alive() then
local x, y, on = renderer.world_to_screen(h:position())
if on then
local w = renderer.measure_text(font, h:health() .. "")
renderer.text(font, x - w / 2, y - 60, color(255, 90, 90, 255), h:health() .. "")
end
end
end
end
return sui
9菜单。控件在文件顶层声明一次,热重载后依然保留,通过 :get() 读取值。
- 1第一个参数是 "选项卡.分组",按第一个点拆分;没有点时,控件会放进名为 general 的分组。
- 2设置的键是 "选项卡.分组.标签"。改了标签,保存在它下面的值就全部丢失;只改显示文字请用 c:caption()。
- 3对于 combo 和 listbox,:get() 返回从零开始的索引:options[c:get() + 1]。
ui.checkbox(group, label, default)→ Control复选框。
- groupstring
- "选项卡.分组"。
- labelstring
- 标签,同时也是设置键的一部分。
- defaultboolean
- 首次运行时的值。
ui.slider_int(group, label, min, max, default, opts)→ Control整数滑块;ui.slider_float 多一个小数位数参数。
- min, maxnumber
- 上下限。
- optstable?
- { unit = " ms", min_text = "Off", max_text = "Max" }。
ui.combo(group, label, items, default_index)→ Control下拉列表;ui.multiselect 和 ui.listbox 用法相同。
- itemsstring[]
- 选项。
- default_indexnumber
- 从零开始的索引。
ui.hotkey(group, label, key, mode)→ Control按键绑定。:get() 表示“此刻是否按住”,:key() 返回绑定的键码。
- keynumber
- 默认的虚拟键码。
- modestring?
- "hold"、"toggle"、"always" 或 "off"。
c:get()当前值;c:set(v) 用于写入。
c:on_change(fn)→ Control响应值的变化。返回控件本身,因此可以链式调用。
- fnfunction
- 调用时不带参数,值请用 :get() 读取。
c:depend(other, expected, mode)→ Control让一个控件依附于另一个控件。
- otherControl
- 父控件。
- expectednumber?
- 使当前控件保持可用的值,默认为 1。
- modestring?
- "grey" 显示为灰色,"hide" 从页面上移除。
local enabled = ui.checkbox("Draw.ESP", "Enemy circles", true)
local radius = ui.slider_int("Draw.ESP", "Radius", 100, 900, 400, { unit = " u" })
radius:depend(enabled)
local s = {}
function s.on_draw()
if not enabled:get() then return end
for _, h in ipairs(entities.heroes("enemy")) do
if h:is_alive() then
renderer.circle_3d(h:position(), radius:get(), color(255, 80, 80, 120))
end
end
end
return sml
10在游戏进程内运行 ONNX,可同步执行,也可在工作线程上执行。模型就是脚本旁边的一个普通文件,核心对它一无所知。
- 1onnxruntime.dll 必须放在 evolve.dll 旁边。ml.available() 告诉你它是否存在,ml.gpu_available() 告诉你 DirectML 是否可用。
- 2net:run 是同步的,耗时 0.2-0.5 ms。更重的模型交给 net:submit,下一帧再通过 net:poll 取回结果。
- 3history 返回原始的世界数值。归一化由脚本负责,而不是缓冲区。
ml.load(path, provider)→ Model|nil按路径和 provider 缓存的模型。失败时返回 nil,原因见 ml.last_error()。
- pathstring
- .onnx 文件。
- providerstring?
- 默认 "cpu",也可以是 "gpu" 或 "auto"。
net:run(input)→ Tensor|nil同步运行。
- inputTensor|Tensor[]
- 一个张量,或与输入数量一致的列表。
net:submit(input)在工作线程上异步运行;net:poll() 取回结果,net:busy() 表示仍在计算。
ml.tensor(rows, cols)→ Tensor全零张量。索引从 1 开始:t:set(r, c, v)、t:get(r, c)、t:table()。
- rowsnumber
- 行数。
- colsnumber?
- 列数;不传则得到一个向量。
history.tensor(entity, frames)→ Tensor|nil[frames, 11] 的运动缓冲区,前端用零填充。各通道列在 history.CH 中。
- entityEntity
- 要读取的单位。
- framesnumber
- 历史帧数,从旧到新排列。
history.set_rate(hz)共享缓冲区的采样频率。history.track_all(true) 会把范围从英雄扩大到所有单位,开销明显更高。
- hznumber
- 采样频率,默认 20。
data
11文件、设置、日志和 JSON。脚本在客户端重启后延续状态所需的一切。
- 1你自己的文件放在 fs.datadir(),同一个包里的其他文件从 fs.scriptdir() 查找。不要从报错里的 chunk 名还原路径:Lua 会把它截断到 60 个字符。
- 2整局都在写入的内容请用 fs.append。通过 fs.write 做“读取-修改-写入”循环,耗时随文件大小呈平方增长,写到一半出错还会丢失全部内容。
log.info(msg)向 evolve.log 写一行;相关的还有 log.warn 和 log.error。
fs.read(name)→ string|nil读取整个文件;相关的还有 fs.exists 和 fs.listdir。
- namestring
- 文件路径。
fs.append(name, str)追加写入,会自动创建父目录。在比赛过程中持续记录日志就用它。
fs.datadir()→ string客户端的 data/ 目录,已经创建好。fs.scriptdir() 返回 scripts/ 的绝对路径。
config.write_int(key, value)脚本的类型化存储:read_int、read_float、read_bool、read_string 及对应的 write 方法,另有 config.has 和 config.delete。
- keystring
- 你自己的键,与菜单无关。
- valuenumber
- 值。
config.get(path)按路径读取菜单控件的值;config.set(path, v) 用于写入。
- pathstring
- "选项卡.分组.标签",与 config.export() 使用的键相同。
注意路径不存在时会抛出错误,这是有意为之:否则路径里的拼写错误会悄无声息地表现为一个关闭的设置。
json.encode(value)→ string把表转成字符串;json.decode(str) 做反向转换。
diag.errors()→ table[]脚本错误,每种不同的故障合并为一行:event、script、msg、traceback、count。相关的还有 diag.profile(),它给出每个处理函数的耗时。