A fair few of my plugins have wanted for an easy to use, native-looking interface to expose to other plugins. This is already possible by way of the CallPlugin() function, but to my eyes it's rather ugly, and doesn't immediately show your intent. I'd much rather call atcp.Send("ping", 0) than CallPlugin("asdfghjkl", "SendAtcp", "ping 0").
Sure, you can just define wrappers yourself. But then every plugin that wants to interface with the main plugin has to have that bulk, and that seems pretty inefficient. Plus, since plugins can load in potentially any order, yours may load before the main plugin has been initialized.
This script fixes the first problem, and alleviates the second. The loader is passed the ID of a plugin, and it returns a proxy object. This proxy will load the plugin's public interface when it's accessed in some way, and the process is invisible to the user.
In order to take advantage of this script, the main plugin needs to supply a MUSHclient variable called "Library", containing source code to return a table containing the public interface. Metamethods aren't guaranteed to work properly on the interface, except for __newindex and __index. For example:
<variables>
<variable name="Library"><![CDATA[
local atcp = {}
local ID = "7c08e2961c5e20e5bdbf7fc5"
atcp.EnableModule = function (...)
CallPlugin(ID, "EnableModule", table.concat({...}, ","))
end
atcp.Send = function (msg)
CallPlugin(ID, "SendATCP", tostring(msg))
end
atcp.Filter = function(id, msg, text)
if id == ID then
local sep = text:find("|") or #text+1
id, msg, text = "ATCP", text:sub(1, sep-1), text:sub(sep+1)
end
return id, msg, text
end
return atcp
]]></variable>
</variables>
All the client plugin needs to do is add two lines:
loader = require("loadplug")
atcp = loader.load("7c08e2961c5e20e5bdbf7fc5")
In this case, I'm requesting the interface for my ATCP plugin. At a later point I can attempt to index into this proxy object. If it succeeds, it's as though I had the proxy object all along. If it doesn't, the script fires an error with a very obvious error message.
One call to loader.load() is all that's required to retrieve any plugin library interface.
Here's the script below:
Quote:
local liblist = {
libraries = {
__mode = "k",
},
load = function(self, tbl)
if self.libraries[tbl] == nil then
local lib = GetPluginVariable(tbl.id, "Library")
if lib == nil then
error("[Plugin Library Loader]: Unable to load interface for plugin " .. (tbl.id or "<invalid ID>"), 0)
end
self.libraries[tbl] = loadstring(lib)()
end
return self.libraries[tbl]
end,
}
setmetatable(liblist.libraries, liblist.libraries)
local Proxy = {
__metatable = false,
__newindex = function(tbl, idx, val)
liblist:load(tbl)[idx] = val
end,
__index = function(tbl, idx)
return liblist:load(tbl)[idx]
end,
new = function(self, id)
return setmetatable({id = id}, self)
end,
}
local Loader = {
__newindex = function()
error("Please don't modify this table.")
end,
load = function(id)
if type(id) ~= "string" then
error("Plugin ID must be a string.")
end
return Proxy:new(id)
end,
}
Loader.__index = Loader
-- getmetatable works as expected
-- setmetatable will not work
Loader.__metatable = Loader
return setmetatable({}, Loader)
|