在游戏开发中会经常使用到lua作为游戏逻辑层的脚本语言,各种优势就不说了,虽然平时用的比较多,但对lua语言本身和内部的一些实现并不是很了解,让我们先从lua的require入手来一探require的各种用法吧。

require其实类似与C/C++中的#include,就是加载一个指定名称的模块进来,该模块可以来自于lua,也可能来自于C/C++,在lua虚拟机启动时,默认会给我们预先加载好一些模块,保存在package.loaded中,我们可以实际打印一波看看:

for k, v in pairs(package.loaded) do
print(k, v)
end

可以看到预先加载好的模块名称,一目了然。那么,lua又是从哪些地方去加载模块呢?加载模块又有什么规则呢?这个就是由package.path指定,同样可以实际打印一波看看:

如果我们希望修改lua加载模块的路径,只要修改这个package.path就可以了。

让我们回到前面打印的package.loaded结果,我们发现这个table的value都是table,这不禁让人好奇:require的返回值是什么呢?我们可以自己写一个简单的自定义模块去验证下:

--mypackage.lua
print("hello world")

然后,执行:

require("mypackage")
for k, v in pairs(package.loaded) do
print(k, v)
end

诶,发现自定义模块返回的值是true。这是为什么呢?明明我们的代码里没有任何一句return语句。难道是在没有写返回值的情况下,默认给我们返回true了?那既然如此,手动显式加一句return试试:

--mypackage.lua
print("hello world")
return nil

然后require,我们发现结果还是一样,返回值为true:

其实,lua之所以这么做,是为了避免重复加载同一个模块,每加载一个模块,就将模块的name作为key,模块的返回值(如果有且不为nil)作为value插入到package.loaded中去。这样下次再去加载这个模块时,就无需加载再去执行该模块的代码,直接返回package.loaded对应key的value即可。怎么验证呢?我们可以尝试require一个模块两次试试:

require("mypackage")
require("mypackage")

注意到,hello world只被打印了一次,说明第二次require的时候并没有执行mypackage中的代码。require内部实际上是调用了loadfile接口来进行模块加载,loadfile的返回值是一个函数,执行该函数,相当于执行该模块的代码:

f = loadfile("D:/lua/mypackage.lua")
f()

那么,有没有办法让重复require时都去执行模块的代码呢?答案是显而易见的,只要将package.loaded中对应的key删掉就可以了:

require("mypackage")
package.loaded.mypackage = nil
require("mypackage")

有意思的是,如果我们的模块返回值为false,或者我们设置package.loaded.mypackage = false时,无论require多少次,都会触发模块的加载执行。不过根据我们之前的验证,这也是符合情理的hhh。说到这里,其实我们就可以自己写一个简单的require了:

function require_ex(module)
if package.loaded[module] then
return package.loaded[module]
end for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do
local path = string.gsub(pattern, '%?', module)
local fp = loadfile(path)
if fp then
local ret = fp()
if ret ~= nil then
package.loaded[module] = ret
else
package.loaded[module] = true
end
return package.loaded[module]
end
end
end

有时候,我们希望require进来的模块是不允许定义全局变量的,因为全局变量会污染我们整个环境,并可能造成意想不到的后果,在lua 5.1,我们可以使用setfenv函数来设置函数环境,而在lua 5.2以上版本,则可以通过修改env参数来解决,loadfile的第三个参数就是函数环境:

    local env = {}
setmetatable(env, {__index = _G, __newindex = function(t, k, v) print("forbidden global var ", k) end})
local fp = loadfile(path, nil, env)

如果lua在package.path中找不到对应的lua模块,那么接下来它会尝试从C++模块中加载,类似地,C++路径是由package.cpath指定的:

针对dll,require内部是使用package.loadlib方法实现的,它接受两个参数,一是模块的路径,二是给lua调用的函数名称(lua_openxxx)。其他的就基本和前面加载lua模块一致了,完整的require_ex代码如下:

function require_ex(module)
if package.loaded[module] then
return package.loaded[module]
end for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do
local path = string.gsub(pattern, '%?', module)
local env = {}
setmetatable(env, {__index = _G, __newindex = function(t, k, v) print("forbidden global var ", k) end})
local fp = loadfile(path, nil, env)
if fp then
local ret = fp()
if ret ~= nil then
package.loaded[module] = ret
else
package.loaded[module] = true
end
return package.loaded[module]
end
end for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do
local path = string.gsub(pattern, '%?', module)
local fp = package.loadlib(path, "luaopen_" .. module)
if fp then
local ret = fp()
if ret ~= nil then
package.loaded[module] = ret
else
package.loaded[module] = true
end
return package.loaded[module]
end
end
end

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

Lua的require小结的更多相关文章

  1. Lua的require和module小结

    Lua的require和module小结  module特性是lua5.1中新增的,用于设置Lua文件自己的模块,最常用的方式是module(name,package.seeall),有时候lua文件 ...

  2. Lua 字符串函数小结

    1.求字符串长度 string.len(str) 2.大小写转换 string.upper(str) string.lower(str) 3.字符串查找(非全局) --func_string.lua ...

  3. Lua脚本认知小结

    0.前言 Lua是一种脚本语言,笔者在学习cocos2dx的时候认识了这个脚本语言. 据个人了解的脚本语言最大的优势是无需编译,使用其内核可以使其跨平台运行. JavaScript,Python,Pe ...

  4. Lua中的require

    lua中的require机制    为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关 ...

  5. Lua Require函数

    转自:http://www.cppblog.com/cslover/archive/2013/12/21/204934.html Lua提供高级的require函数来加载运行库.粗略的说require ...

  6. 【转载】lua中的require机制

    [转载自]http://blog.chinaunix.net/uid-552961-id-2736410.html lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然 ...

  7. Lua中的require(转)

    lua中的require机制    为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关 ...

  8. lua中的require机制

    lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关的数据 ...

  9. Lua require 相对路径

    lua require 加载方式与我们现在熟知的路径系统不太一样,想要知道lua require 方法的工作原理也很简单 随便写一个错误的require 代码即可: 1 require("l ...

随机推荐

  1. ABBYY FineReader 14新增了什么

    FineReader 是一款一体化的 OCR 和PDF编辑转换器,随着版本的更新,功能的增加,FineReader 14的推出继续为用户在处理文档时提高业务生产力,该版本包含若干新特性和功能增强,包括 ...

  2. EasyRecovery——信息时代的“后悔药”

    前几日,小编在豆瓣潜水的时候看到这么一个帖子,说是一对小情侣吵架,女方一气之下把男方的博士论文和资料全删了,求一个办法让男友消气. 站在吃瓜的角度,小编和广大群众看法一致,希望两人直接分手,放男方一条 ...

  3. FL Studio水果音乐制作入门教程

    "没有早期音乐教育,干什么事我都会一事无成".这并非某位音乐家精心熬制的心灵鸡汤,而是出自物理学家爱因斯坦之口,朋友们没有看错,就是那个被称为二十世纪伟大科学家的爱因斯坦,所以,别 ...

  4. jQuery 第四章 实例方法 DOM操作之data方法

    jquery 里面 的 data 方法比较重要, 所以成一个模块写: 首先, 得知道 data()  干嘛用的, 看淘宝上 有自定义的属性, 为data -  什么什么,   这是为了dom 跟数据有 ...

  5. 完全图的最短Hamilton路径——状压dp

    题意:给出一张含有n(n<20)个点的完全图,求从0号节点到第n-1号节点的最短Hamilton路径.Hamilton路径是指不重不漏地经过每一个点的路径. 算法进阶上的一道状压例题,复杂度为O ...

  6. 【???】今天上午的考试题——区间dp和字符串/线性筛的综合应用

    T3还没有打出来,就先放两道. ---------------------------------------------------------- T1:密码破译 温温手下的情报部门截获了一封加密信 ...

  7. 3.深入Istio:Pilot配置规则ConfigController

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的Istio源码是 release 1.5. Config Controller ...

  8. websocket服务端开发

    基于http请求以拉的方式去做服务器的推送,无论是实时性和有效字节都是差强人意的效果. 公司的im系统在与客户端的交互上实际上借助了websocket来实现服务器与客户端的事实消息推送,今天就来简单了 ...

  9. CentOS 6.5 iso系统定制

    前言 更改CentOS6.5背景图片.CentOS标题为DntOS,总之就是用ISO安装或者安装后的系统启动时不能有CentOS标志. ISO光盘目录介绍: (1)isolinux 目录存放光盘启动时 ...

  10. 思维导图学 Kotlin

    前言 最近做了<Kotlin实战>的思维导图笔记,Kotlin真香-- 目录 基础 函数 类.对象 λ表达式 类型 约定 高阶函数.泛型 公众号 coding 笔记.点滴记录,以后的文章也 ...