0%

关于lua-MUD的探究

一直在找寻一个比较好用,现代的 MUD 服务端,LPCMUD 不错,但是相对于现代的工具和语言而说,还是有点不行了。我看上了 skynet,所以就想找一个 lua 的实现,这不,就找到了 lua-MUD

前言

就我而言,其实 LPMUD 中的 es2 那种,将标准对象,实现为一些特性的组合,有点 ECS 的样子。但是在其中,每个特性都是方法和数据的结合,并不是纯粹的 ECS。另外,想要将其进行 JSON 化,比较头疼。再者,开发的效率比较低。所以打算放弃。

Object

lua-MUD,将所有的数据结构,抽象了一个根,Object,它实现组合的方法是通过对 Object 的元表的 __index 方法来访问其内部的一个名叫 __middleware 属性进行调用各种方法。

然后会为 Object 新建的对象,挂上 scripts

Object.new = function(self,o)
-- Either create a new object, or turn existing table into an instance
local o = o or {}
setmetatable(o, self)
if not o.identifier then
o.identifier = db.reserve_id()
end
if not o.scripts then
o.scripts = {"object"}
end
o:updateScripts()

return o
end
function Object:updateScripts()
self.__middleware = Middleware()
local loaded = {}
for i = 1, #self.scripts do
local k = self.scripts[i]
self:loadScript(k, loaded)
end

--[[
if exists("scripts.objscripts."..id) then
self:loadScript("objscripts."..id, loaded)
end
]]
end

function Object:loadScript(scriptName, loaded)
loaded = loaded or {}
if loaded[scriptName] then return end
local success, script = pcall(require, "scripts."..scriptName)
if not success then
print("Error loading script \""..scriptName..'"')
print(tostring(script))
return nil
end
-- 这会将脚本的依赖,以加载进来,放到 loaded 表中。
loaded[scriptName] = script
if script.dependencies then
for j = 1, #script.dependencies do
self:loadScript(script.dependencies[j], loaded)
end
end

-- 这里是一个数据项的遍历
for k,v in pairs(script.data or {}) do
local t, key = utils.resolve(self, k)
if t then
if t[key] == nil then
t[key] = utils.deepcopy(v)
end
end
end

-- 将 insert 中的内容,放到 __middleware 的前面
for methodName,middleware in pairs(script.insert or {}) do
for j = 1, #middleware do
local func, pos = unpack(middleware[j])
if pos < 0 then
pos = #(self.__middleware[methodName] or {}) + pos + 1
end
self.__middleware:insert_middleware(methodName, func, pos)
end
end

-- 将 script 的方法,放在 __middleware 的后方,这里需要注意的是,middler 都可能有多个方法,都会添加进去。
for methodName,middleware in pairs(script.methods or {}) do
for j = 1, #middleware do
local func = middleware[j]
self.__middleware:append_middleware(methodName, func)
end
end
end

因此,就可以将多个特性或者脚本,挂到一个对象上了。

方法的调用

我们在 调用 Object.new() 的时候,将新对象的元表设置为了 Object,当访问不到新对象中的键时,就会访问元表中的 __index 元方法,也就是 Object.__index()

Object.__index = function(self, key)
local v = Object[key]
if v then
return v
elseif rawget(self, "__middleware") and rawget(self.__middleware, key) then
-- TODO: Should calls to nonexistent
-- methods error or just do nothing?
-- Polymorphism vs. ease of debugging
return function(self, ...)
return self.__middleware:call(self, key, {...})
end
elseif key:match("^get") then
return function(self)
return rawget(self, key:sub(4, 4):lower() .. key:sub(5, -1))
end
end
end

在元方法中,如果访问不到当前键,那么就会采用 rawget 的形式来在 __middleware 里面查找,不会触发元方法。

另外,还对 get 开头的方法做了一个假设。如我们调用 getRoom 时,会将第 4 个字符变成小写来在 __middler 里面进行查找。