initial release
This commit is contained in:
170
cosmic rage/lua/ppi.lua
Normal file
170
cosmic rage/lua/ppi.lua
Normal file
@@ -0,0 +1,170 @@
|
||||
--[[
|
||||
|
||||
PLUGIN-to-PLUGIN-INTERFACE (PPI)
|
||||
|
||||
Author: Twisol
|
||||
Date: 3rd January 2010
|
||||
|
||||
Amendments: Nick Gammon
|
||||
Date: 8th January 2010
|
||||
|
||||
Example of use:
|
||||
|
||||
SERVICE
|
||||
|
||||
-- require PPI module
|
||||
require "ppi"
|
||||
|
||||
-- exposed function
|
||||
function SomeMethodHere (a, b, c, d)
|
||||
-- do something with a, b, c, d
|
||||
return 1, 2, 3, 4
|
||||
end
|
||||
|
||||
-- notify PPI of this function
|
||||
ppi.Expose "SomeMethodHere"
|
||||
|
||||
-- Or, for anonymous functions:
|
||||
ppi.Expose ("DoSomethingElse", function () print "hello" end)
|
||||
|
||||
CLIENT
|
||||
|
||||
-- require PPI module
|
||||
require "ppi"
|
||||
|
||||
-- resolve dependencies
|
||||
function OnPluginListChanged ()
|
||||
|
||||
-- get PPI entries for all exposed function in this plugin
|
||||
my_service = ppi.Load ("15783160bde378741f9652d1") -- plugin ID of service plugin
|
||||
|
||||
if not my_service then
|
||||
Note ("Dependency plugin not installed!")
|
||||
end
|
||||
|
||||
end -- OnPluginListChanged
|
||||
|
||||
-- later on in plugin ...
|
||||
|
||||
-- call SomeMethodHere in other plugin, passing various data types, getting results
|
||||
|
||||
if my_service then
|
||||
w, x, y, z = my_service.SomeMethodHere (42, "Nick", true, { a = 63, b = 22 } )
|
||||
end -- if service installed
|
||||
|
||||
NOTES
|
||||
-----
|
||||
|
||||
ppi.Load returns a table with various values in it about the target plugin (see below
|
||||
for what they are). For example, _name is the plugin name of the target plugin, and
|
||||
_version is the version number of that plugin.
|
||||
|
||||
If ppi.Load returns no value (effectively, nil) then the target plugin was not installed.
|
||||
|
||||
Provided a non-nil result was returned, you can then call any exposed function in the
|
||||
target plugin. There is currently no mechanism for finding what functions are exposed, for
|
||||
simplicity's sake. However it would be possible to make a service function that returned all
|
||||
exposed functions. If service plugins evolve in functionality, checking the target plugin's
|
||||
version (the _version variable) should suffice for making sure plugins are synchronized.
|
||||
|
||||
To avoid clashes in variable names, you cannot expose a function starting with an underscore.
|
||||
|
||||
Communication with the target plugin is by global variables set up by the Expose function, along
|
||||
the lines of:
|
||||
|
||||
PPI_function_name_PPI_ (one for each exposed function)
|
||||
|
||||
Also:
|
||||
|
||||
PPI__returns__PPI_ is used for storing the returned values.
|
||||
|
||||
--]]
|
||||
|
||||
-- hide all except non-local variables
|
||||
module (..., package.seeall)
|
||||
|
||||
-- for transferring variables
|
||||
require "serialize"
|
||||
|
||||
-- PPI version
|
||||
local V_MAJOR, V_MINOR, V_PATCH = 1, 1, 0
|
||||
local VERSION = string.format ("%d.%d.%d", V_MAJOR, V_MINOR, V_PATCH)
|
||||
|
||||
-- called plugin uses this variable to store returned values
|
||||
local RETURNED_VALUE_VARIABLE = "PPI__returns__PPI_"
|
||||
|
||||
-- For any function in our PPI table, try to call that in the target plugin
|
||||
local PPI_meta = {
|
||||
__index = function (tbl, idx)
|
||||
if (idx:sub (1, 1) ~= "_") then
|
||||
return function(...)
|
||||
-- Call the method in the target plugin
|
||||
local status = CallPlugin (tbl._id, "PPI_" .. idx .. "_PPI_", serialize.save_simple {...})
|
||||
|
||||
-- explain a bit if we failed
|
||||
if status ~= error_code.eOK then
|
||||
ColourNote ("white", "red", "Error calling " .. idx ..
|
||||
" in plugin " .. tbl._name ..
|
||||
" using PPI from " .. GetPluginName () ..
|
||||
" (" .. error_desc [status] .. ")")
|
||||
check (status)
|
||||
end -- if
|
||||
|
||||
-- call succeeded, get any returned values
|
||||
local returns = {} -- table of returned values
|
||||
local s = GetPluginVariable(tbl._id, RETURNED_VALUE_VARIABLE) or "{}"
|
||||
local f = assert (loadstring ("t = " .. s)) -- convert serialized data back
|
||||
setfenv (f, returns) () -- load the returned values into 'returns'
|
||||
|
||||
-- unpack returned values to caller
|
||||
return unpack (returns.t)
|
||||
end -- generated function
|
||||
end -- not starting with underscore
|
||||
end -- __index function
|
||||
} -- end PPI_meta table
|
||||
|
||||
-- PPI request resolver
|
||||
local function PPI_resolver (func)
|
||||
return function (s) -- calling plugin serialized parameters into a single string argument
|
||||
local params = {} -- table of parameters
|
||||
local f = assert (loadstring ("t = " .. s)) -- convert serialized data back
|
||||
setfenv (f, params) () -- load the parameters into 'params'
|
||||
|
||||
-- call target function, get return values, serialize back into variable
|
||||
SetVariable(RETURNED_VALUE_VARIABLE, serialize.save_simple {func(unpack (params.t))})
|
||||
end -- generated function
|
||||
|
||||
end -- PPI_resolver
|
||||
|
||||
-- EXPOSED FUNCTIONS
|
||||
|
||||
-- We "load" a plugin by checking it exists, and creating a table saving the
|
||||
-- target plugin ID etc., and have a metatable which will handle function calls
|
||||
function Load (plugin_id)
|
||||
if IsPluginInstalled (plugin_id) then
|
||||
return setmetatable (
|
||||
{ _id = plugin_id, -- so we know which plugin to call
|
||||
_name = GetPluginInfo (plugin_id, 1),
|
||||
_author = GetPluginInfo (plugin_id, 2),
|
||||
_filename = GetPluginInfo (plugin_id, 6),
|
||||
_enabled = GetPluginInfo (plugin_id, 17),
|
||||
_version = GetPluginInfo (plugin_id, 18),
|
||||
_required_version = GetPluginInfo (plugin_id, 19),
|
||||
_directory = GetPluginInfo (plugin_id, 20),
|
||||
_PPI_V_MAJOR = V_MAJOR, -- version info
|
||||
_PPI_V_MINOR = V_MINOR,
|
||||
_PPI_V_PATCH = V_PATCH,
|
||||
_PPI_VERSION = VERSION,
|
||||
},
|
||||
PPI_meta) -- everything except the above will generate functions
|
||||
else
|
||||
return nil, "Plugin ID " .. plugin_id .. " not installed" -- in case you assert
|
||||
end -- if
|
||||
end -- function Load
|
||||
|
||||
-- Used by a plugin to expose methods to other plugins
|
||||
-- Each exposed function will be added to global namespace as PPI_<name>_PPI_
|
||||
function Expose (name, func)
|
||||
assert (type (func or _G [name]) == "function", "Function " .. name .. " does not exist.")
|
||||
_G ["PPI_" .. name .. "_PPI_"] = PPI_resolver (func or _G [name])
|
||||
end -- function Expose
|
||||
Reference in New Issue
Block a user