NPL(Neural Parallel Language)是神经元并行语言的缩写,是大富网络技术有限公司开发的一种基于 Lua 语言的编程环境。NPL 的设计理念类似人类神经元的结构,将复杂计算分解到节点,简洁高效,特别适合于分布式计算、大型网络应用。
NPL 是一种解释性语言,包含了一个运行时环境,提供了完善的组件和接口,包括基本组件、网络组件和一套完整的 3D 引擎系统,并且体积只有 10MB,在任何一台计算机上都可以快速部署。
NPL 官方文档:NPLRuntime

NPL Runtime Architecture
直接使用 git 下载:git clone
或从 NPL 官方地址下载:官方下载

将下载得到的 ParaCraftSDK-master.zip 解压到任意文件夹下。
运行 redist\ParaCraft.exe,点 开始 会自动更新。

运行 NPLRuntime\install.bat,安装完成。
当前版本有个小 bug,可能不能成功注册环境变量。此时需要手动将解压路径 \NPLRuntime\win\bin 加入到环境变量中,这样就可以在任何路径下运行NPL程序了。
print("hello world!")
ParaGlobal.Exit(0)
npl helloworld.lua

NPL 的语法基于 Lua 语言。Lua 语言是一种高效的脚本语言。官方网站是:http://www.lua.org/
这里是一个快速的语法介绍,旨在让阅读者能够以最快的速度了解基本语法,更详细的内容请参阅官方推荐的教材:推荐教材
或者此处的中文教材:中文教材
如果你已经对Lua语法很熟悉可以直接跳到第14章。
一行为一个语句:
a=0
print(a)
两个减号后为单行注释:
-- 这是一行注释
多行注释用--[[开头,
]]结束:
--[[
多行注释
多行注释
多行注释
]]
以下为Lua保留的关键字,不可用作变量或函数名称:
and、break、do、else、elseif、end、false、
for、function、if、in、local、nil、not、or、
repeat、return、then、true、until、while
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
| 数据类型 | 描述 |
|---|---|
| nil | 只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
| boolean | 包含两个值:false和true。 |
| number | 表示双精度类型的实浮点数 |
| string | 字符串由一对双引号或单引号来表示 |
| function | 由 C 或 Lua 编写的函数 |
| userdata | 表示任意存储在变量中的C数据结构 |
| thread | 表示执行的独立线路,用于执行协同程序 |
| table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
a=10 --a为number类型
b="hello" --b为string类型
Lua 中直接定义的变量全部是全局变量,包括函数中的变量,除非用 local 显式声明为局部变量。
a=5
function test()
b=10
local c=15
end
test()
print(a,b,c) --输出为5 10 nil
假设 a=10,b=20
| 操作符 | 描述 | 实例 |
|---|---|---|
| + | 加法 | a+b=30 |
| - | 减法 | a-b=-10 |
| * | 乘法 | a*b=200 |
| / | 除法 | a/b=0.5 |
| % | 取余 | a%b=10 |
| ^ | 乘幂 | a^2=100 |
| - | 负号 | -a=-10 |
假设 a=10,b=20
| 操作符 | 描述 | 实例 |
|---|---|---|
| == | 等于 | (a==b)=false |
| ~= | 不等于 | (a~=b)=true |
| > | 大于 | (a>b)=false |
| < | 小于 | (a<b)=true |
| >= | 大于或等于 | (a>=b)=false |
| <= | 小于或等于 | (a<=b)=true |
假设a=true,b=false
操作符 描述 实例
and 逻辑与 (a and b)=false
or 逻辑或 (a or b)=true
not 逻辑非 not a= false
假设a="Hello",b="World"
操作符 描述 实例
.. 连接两个字符串 a..b=HelloWorld
高 | | 低 ^ not -(负号)
* /
+ -
..
< > <= >= ~= ==
and
or
if (bool表达式) then
bool表达式为true时执行的语句
end
if (bool表达式) then
bool表达式为true时执行的语句
else
bool表达式为false时执行的语句
end
if (bool表达式1) then
bool表达式1为true时执行的语句
elseif (bool表达式2) then
bool表达式2为true时执行的语句
else
以上表达式都为false时执行的语句
end
语法:
for var=exp1,exp2,exp3 do
语句块
end
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长,exp3 为可选,不指定则为 1
实例:
for i=1,10 do
print(i)
end
for i=10,1,-1 do
print(i)
end
语法:
for i,v in ipairs(a) do
print(v)
end
打印数字a中的所有值
实例:
days = {"Suanday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
for i,v in ipairs(days) do
print(v)
end
语法:
while(bool表达式) do
bool表达式为true时执行的语句
end
实例:
i=0
while(i<20) do
print(i)
i = i+1
end
语法:
repeat
语句块
until(bool表达式)
实例:
i=0
repeat
print(i)
i=i+1
until(i>20)
跳出循环。
函数返回。
(local) function function_name (arg1, arg2, arg3, … argn)
函数体
return 返回值
end
和变量一样,local为作用域,默认为全局函数,加 local 为局部函数。
多个参数时用逗号隔开,调用时如果传入的参数不足,其余参数为 nil
function test(a, b, c)
print(a,b,c)
end
test(1,2) --输出为1 2 nil
Lua 支持可变参数,函数的参数放在一个名为 arg 的表中,#arg 表示参数个数
function test(...)
local arg={...}
for i,v in ipairs(arg) do
print(v)
end
print(“参数个数=”..#arg)
end
Lua 支持多返回值,与相应数目的变量直接赋值即可
function swap(a,b)
return b,a
end
i=1
j=2
i,j=swap(i,j)
print("i="..i,"j="..j) --输出i=2 j=1
Lua 中的 table 实际是一个 Key-Value 结构的关联数组,使用前需要先初始化:
mytalbe={} --创建一个空表
创建之后可以直接添加元素:
mytable[1]="Monday"
mytable["hello"]="world"
或在创建时直接添加:
mytable={[1]="Monday", hello="world"} --与上面的创建方式等价
如果用 key 全用数字(相当于数组)创建时可以简略写成:
mytable = {"Monday", "Tuesday","Wednesday"}
但此时下标是从1开始的,即 mytable[1]="Monday"
如果 key 为字符串,可以用 . 的形式访问: mytable.hello 等价于 mytable["hello"]
遍历可使用泛型 for:
for k,v in pairs(mytable) do --一般table
print(k,v)
end
for i,v in ipairs(mytable) do --数字下标的table
print(i,v)
end
metastable 可以用来改变table的行为,如 __index 表示查找元素的行为:
mt = { foo = 3 }
t = {}
setmetatable(t, { __index = mt })
print(t.foo)
在执行 t.foo 时,由于 t 没有 foo 这个 key 对应的 value,会到 metatable 中去查找,metatable 中有 __index 这个 key 对应的值 mt,所以最终会到 mt 中去查找 foo 对应的 value,最后得到3。必须理解 metatable 才能理解下一节中面向对象的实现方法。
Lua 也支持面向对象的编程,实际上是利用 table 来实现的:
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
创建了一个 Account 对象,它有一个 balance 的成员和一个 withdraw 方法。
上面的 Account 是一个对象,如果想定义一个类,添加方法时需要用 : 创建一个 self 参数,并且需要一个 new 方法:
Account = {balance = 0}
function Account:new (o) --等价于function Account.new(self, o)
o = o or {} --创建一个新的table
setmetatable(o, self) --非常重要,请看下面的详细说明
self.__index = self
return o
end
function Account:withdraw (v) --等价于function Account.withdraw(self, v)
self.balance = self.balance - v
end
--使用Account类
a=Account:new() --创建一个新的对象a
a:withdraw(10) --调用withdraw方法
在 Account:new() 中的:
setmetatable(o, self)
self.__index = self
为新对象o设置了一个 metatable,改变了 __index 的行为,对于:
a=Account:new()
在执行 new 函数的时候,o的 metatable 被设置成了 Account,a=o(这时是个空表),那么在:
a:withdraw(10)
执行的时候,由于 a 没有 withdraw 这个方法,结果通过 metatable 调用了:
Account.withdraw(a, 10)
即Account的withdraw方法,参数里的self是a本身
简单模式一次处理一个文件,直接使用io操作:
file = io.open("test.lua", "r") --只读打开文件
io.input(file) --设置io的输入文件
print(io.read()) --读取文件第一行并打印
io.close(file) --关闭文件
如果同时要处理多个文件就需要使用完全模式,即使用 file:function 代替 io.function:
file = io.open("test.lua", "r") --只读打开文件
print(file:read()) --读取文件第一行并打印
file:close() --关闭文件
从本章开始介绍 NPL 的特性。NPL作为一个完整的运行时环境有其独特的运行机制,简单的说是一个分布式的运行调度方式,基于Actor模型。理解了运行模型,才能真正理解NPL。
Actor 这个模型由 Carl Hewitt 在1973年提出,是一个并行计算的数学模型。它的理念非常简单: 1) 万物皆 Actor,每个 Actor 是完全独立的,可以同时执行它们的操作 2) Actor 之间通过发送消息来通信,消息通过一个消息队列来处理 3) 每一个 Actor 是一个计算实体,映射接收到的消息到以下动作
NPL Runtime 调度模型 当我们在本地执行:npl hello.lua 创建了一个主进程,这个进程又创建了一个主线程来执行 hello.lua (实际还创建了其它线程用来运行runtime的不同组件),每个线程有一个消息队列,被执行的文件会被加入到消息队列中。
Activate(激活)文件 在程序中,用 NPL.activate(url, {data}) 激活一个文件,data是传递的数据:
hello.lua中执行:
NPL.CreateRuntimeState("T1", 0):Start(); --创建名为T1的线程
NPL.activate("(T1)test1.lua", {"hello"}) --在T1线程中激活test.lua
test1.lua 代码
local function activate()
print(msg[1]); --打印出"hello"
end
NPL.this(activate); --设置activate的入口为activate()函数
一个文件被激活后,它将会加入到指定线程中运行,接收消息,处理消息和发送消息。
NPL.activate 的本质是向某个文件发送消息,如果这个文件不在指定线程的消息队列中,则会将它加入到线程中,如果已经在消息队列中,则只是发送消息到消息队列里。
分布式 NPL.activate 的第一个参数,即文件的url,可以是本地文件,也可以是一个远程的文件,url 参数的格式是:
[(sRuntimeStateName|gl)][sNID:]sRelativePath[]
举例:
(gl)script/hello.lua
(worker1)script/hello.lua
(world1)server001:script/hello.lua
从NPL运行模型可知,多线程是NPL的基本运行方式,使用多线程只需要用 NPL.CreateRuntimeState 创建线程,然后用 NPL.activate 调用相关的文件加载到相关线程运行就可以了。
NPL.CreateRuntimeState("T1", 0):Start();
NPL.activate("(T1)test1.lua", {"hello1"})
NPL.CreateRuntimeState("T2", 0):Start();
NPL.activate("(T2)test2.lua", {"hello2"})
一般情况下消息驱动的模型只需要将要运行的同类文件加载到同一线程就可以了。 Lua 本身还支持一种名为 coroutine(协同程序)的类似线程的机制,但它实际是用单线程模拟多线程,需要用程序来控制不同 coroutine 的协同工作,使用比较复杂,也不符合NPL的设计理念,在NPL中不推荐使用。
从 NPL 运行模型可知,网络是 NPL 的内置属性,用 NPL.activate 可以无需知道网络的细节,直接根据url进行传输,运行时环境会自动建立连接和传输数据。 下面是一个远程传输的例子:
Server 端:
hello.lua
local function activate()
print("hello world")
end
NPL.this(activate)
server.lua
print("server start")
NPL.StartNetServer("127.0.0.1", "60001");
NPL.AddPublicFile("hello.lua", 1);
Client 端:
client.lua
print("client start")
NPL.StartNetServer("0", "0")
NPL.AddNPLRuntimeAddress({host="127.0.0.1", port="60001", nid="server1"})
while( NPL.activate("server1:hello.lua", {"remote activate"}) ~=0 ) do
print("failed to send message");
ParaEngine.Sleep(1);
end
测试时打开两个命令行分别执行:
npl bootstrapper="server.lua" logfile="server.txt"
npl bootstrapper="client.lua" logfile="client.txt"
在 server.txt 中可以看到输出:
server start --server运行后的输出
hello world --client连接上之后激活hello.lua的输出
使用库首先要用 NPL.load 加载 commonlib.lua 和需要的库文件:
NPL.load("(gl)script/ide/commonlib.lua")
NPL.load("(gl)mylib.lua")
要使用库中的类,先使用 commonlib.gettable 来获取库的类:
myclass = commonlib.gettable("myclass");
以下是一个完整的例子 Account.lua:
Account = {balance = 0}
function Account:new (o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw (v)
self.balance = self.balance - v
end
test.lua:
NPL.load("(gl)script/ide/commonlib.lua");
NPL.load("Account.lua");
local Account = commonlib.gettable("Account");
a=Account:new({balance=100})
a:withdraw(10)
print(a.balance) --输出90
如果要从原有的类派生新类,要使用 commonlib.inherit:
newclass = commonlib.inherit(nil, commonlib.gettable("myclass"))
在新的类中 ctor() 函数为构造函数。一个例子:
NPL.load("(gl)script/ide/commonlib.lua");
NPL.load("Account.lua");
local NewAccount= commonlib.inherit(nil, commonlib.gettable("Account"))
function NewAccount:ctor() --构造函数
self.balance = 100
end
function NewAccount: deposit(v) --增加的方法
self.balance = self.balance+v
end
a=Account:new()
a:deposit(10)
print(a.balance) --输出110
NPL 带有一个 Web 服务器,它类似一个 PHP 服务器,可以处理用户请求的页面,运行该页面中内嵌的 NPL 脚本,并产生结果的html页面返回给用户。
1) 从 git 上下载 WebServer 组件:
git clone https://github.com/NPLPackages/main.git
2) 在 main\script\apps\WebServer 下执行:
npl bootstrapper="WebServer.lua" port="80" root="test/" servermode="true"
port参数为端口号,root参数为web服务的目录
3) 用浏览器打开 http://localhost/index.page ,显示测试页面
页面程序是在 html 页面中嵌入NPL脚本,用户请求该页面时,server 会执行脚本并将结果返回给用户:
<html>
<body>
<?npl
for i=1,5 do
echo "<p>hello</p>"
end
?>
</body>
</html>
其中 <?npl ?> 标记间的代码会被服务器端执行,
NPL 基于 Lua 语言,因此 Lua 支持的数据库都可以支持。以下是几种数据库的使用方法:
Table Database是NPL内置的数据库,是一个具有大部分标准数据库功能的小型数据库,简单高效,在数据不是特别复杂的情况下非常适用。
NPL 还内置了 MySql 驱动,可以直接连接使用MySql数据库,详见 https://github.com/LiXizhi/NPLRuntime/wiki/UsingMySql
要连接使用其它第三方数据库,需要使用开源的 LuaSQL,官方网站是 https://github.com/keplerproject/luasql ,具体使用方法请参考官方文档。

