[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Tips and tricks
. . -> [Subject]  PPI - A plugin communications script

PPI - A plugin communications script

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Pages: 1  2 3  4  5  6  7  8  9  10  

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #15 on Fri 08 Jan 2010 10:08 AM (UTC)
Message
I acceidentally left off the code for cleaning up after the request, heh. Re-pasted and post edited, if by some miracle someone downloaded it before I fixed it, please download it again.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #16 on Fri 08 Jan 2010 06:06 PM (UTC)
Message
New version v1.0.1, fixing the clean-up of variables I somehow missed, as well as fixing the array ID deserialize() would use so it would be unique within a given deserialization.

local __V_MAJOR, __V_MINOR, __V_PATCH = 1, 0, 1
local __VERSION = string.format("%d.%d.%d", __V_MAJOR, __V_MINOR, __V_PATCH)
 
local PPI_list = {}
 
local myID = GetPluginID()
local myPPI = {}
 
local array_id   = function(id)      return "PPIarray_" .. id   end
local params_id  = function(id)      return "PPIparams_" .. id  end
local method_id  = function(id)      return "PPImethod_" .. id  end
 
local request_id = function(id) return "PPI_" .. id .. "_REQUEST" end
local cleanup_id = function(id) return "PPI_" .. id .. "_CLEANUP" end
 
 
function serialize(params, params_list)
  if not params_list then
    local params_list = {}
    serialize(params, params_list)
   
    local list = {}
    for k,v in ipairs(params_list) do
      list[k] = v
    end
    return list
  end
 
  if params_list[params] then
    return params_list[params]
  end
 
  local index = #params_list + 1
  params_list[params] = index
  params_list[index] = true
 
  local id = array_id(index)
  ArrayCreate(id)
 
  for k,v in pairs(params) do
    local key = nil
    if type(k) == "string" then
      key = "s:" .. k
    elseif type(k) == "number" then
      key = "n:" .. k
    end
   
    if key then
      local value = "z:~"
     
      if type(v) == "string" then
        value = "s:" .. v
      elseif type(v) == "number" then
        value = "n:" .. v
      elseif type(v) == "boolean" then
        value = "b:" .. (v and "1" or "0")
      elseif type(v) == "table" then
        value = "t:" .. serialize(v, params_list)
      end
     
      ArraySet(id, key, value)
    end
  end
 
  params_list[index] = ArrayExport(id, "|")
  ArrayDelete(id)
 
  return index
end
 
function deserialize(data_list, index, state)
  if not index or not state then
    return deserialize(data_list, 1, {})
  end
 
  if state[index] then
    return state[index]
  end
 
  local tbl = {}
  state[index] = tbl
 
  local id = array_id(index)
  ArrayCreate(id)
  ArrayImport(id, data_list[index], "|")
 
  for k,v in pairs(ArrayList(id)) do
    local key_type = k:sub(1,1)
    local key = nil
   
    if key_type == "s" then
      key = k:sub(3)
    elseif key_type == "n" then
      key = tonumber(k:sub(3))
    end
   
    if key then
      local item_type = v:sub(1,1)
      local item = v:sub(3)
     
      if item_type == "s" then
        tbl[key] = item
      elseif item_type == "n" then
        tbl[key] = tonumber(item)
      elseif item_type == "b" then
        tbl[key] = ((item == "1") and true or false)
      elseif item_type == "t" then
        tbl[key] = deserialize(data_list, tonumber(item), state)
      else
        tbl[key] = nil
      end
    end
  end
 
  ArrayDelete(id)
 
  return tbl
end
 
local function request(id, func_name, ...)
  -- Prepare the arguments
  local params = {...}
  for k,v in ipairs(serialize(params)) do
    SetVariable(params_id(id) .. "_" .. k, v)
  end
 
  -- Call the method
  SetVariable(method_id(id), func_name)
  CallPlugin(id, request_id(id), myID)
 
  -- Clean up the arguments
  for i=1,#params do
    DeleteVariable(params_id(id) .. "_" .. i)
  end
  DeleteVariable(method_id(id))
 
  -- Gather the return values
  local returns = {}
  local i = 1
  while GetPluginVariable(id, params_id(myID) .. "_" .. i) do
    returns[i] = GetPluginVariable(id, params_id(myID) .. "_" .. i)
    i = i + 1
  end
  returns = deserialize(returns)
 
  -- Have the other plugin clean up its return values
  CallPlugin(id, cleanup_id(id), myID)
 
  return unpack(returns)
end
 
-- A 'thunk' is a delayed resolver function.
local function new_thunk(id, func_name)
  return function(...)
    return request(id, func_name, ...)
  end
end
 
-- If the requested function hasn't yet had a thunk created,
-- create a new thunk and return it.
local PPI_meta = {
  __index = function(tbl, idx)
    local thunk = new_thunk(PPI_list[tbl].id, idx)
    tbl[idx] = thunk
    return thunk
  end,
}
 
local PPI = {
  __V = __VERSION,
  __V_MAJOR = __V_MAJOR,
  __V_MINOR = __V_MINOR,
  __V_PATCH = __V_PATCH,
 
  -- Used to retreive a PPI for a specified plugin.
  Load = function(plugin_id)
    if not IsPluginInstalled(plugin_id) then
      return false
    end
   
    local tbl = PPI_list[plugin_id]
    if not tbl then
      tbl = setmetatable({}, PPI_meta)
      PPI_list[tbl] = {id = plugin_id}
      PPI_list[plugin_id] = tbl
    end
    return tbl
  end,
 
  -- Used by a plugin to expose methods to other plugins
  -- through its own PPI.
  Expose = function(name, func)
    myPPI[name] = (func and func or _G[name])
  end,
}
 
-- PPI request resolver
_G[request_id(myID)] = function(id)
  -- Get requested method
  local func = myPPI[GetPluginVariable(id, method_id(myID))]
  if not func then
    return
  end
 
  -- Deserialize parameters
  local params = {}
  local i = 1
  while GetPluginVariable(id, params_id(myID) .. "_" .. i) do
    params[i] = GetPluginVariable(id, params_id(myID) .. "_" .. i)
    i = i + 1
  end
  params = deserialize(params)
 
  -- Call method, return values
  local returns = {func(unpack(params))}
  for k,v in ipairs(serialize(returns)) do
    SetVariable(params_id(id) .. "_" .. k, v)
  end
end
 
-- Return value cleaner
_G[cleanup_id(myID)] = function(id)
  local i = 1
  while GetVariable(params_id(id) .. "_" .. i) do
    DeleteVariable(params_id(id) .. "_" .. i)
    i = i + 1
  end
end
 
return PPI

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #17 on Fri 08 Jan 2010 08:44 PM (UTC)

Amended on Fri 08 Jan 2010 08:45 PM (UTC) by Nick Gammon

Message
Twisol said:

On Note() versus error(): It depends on the plugin, really. If it will work acceptably even if the dependency isn't there, sure, a Note() is probably better. But if you absolutely don't want the plugin doing anything at all if the dependency isn't loaded, if things such as collecting information or other stuff are pointless and wasteful if the dependency isn't there, then I would say an error() is the way to go. You can always have the error say to reinstall the plugin, after all.


True, it depends on the situation. However I think showing something like this to the end-user is just confusing for them:


Run-time error
Plugin: ppi_test_client (called from world: SmaugFUSS)
Function/Sub: OnPluginListChanged called by Plugin ppi_test_client
Reason: Executing plugin ppi_test_client sub OnPluginListChanged
[string "Plugin"]:9: Dependency plugin not installed!
stack traceback:
        [C]: in function 'error'
        [string "Plugin"]:9: in function <[string "Plugin"]:3>
Error context in script:
   5 :   my_service = ppi.Load("15783160bde378741f9652d1")
   6 :   if my_service then
   7 :     tprint (my_service)
   8 :   else
   9*:     error("Dependency plugin not installed!")
  10 :   end
  11 :   
  12 : end
  13 : 


They will think they did something wrong, or the plugin is faulty, and you will get a forum query.

Better to display something like:


This plugin requires you to install: Twisols_Stats_Gather plugin.

This is available from: www.somewebsite.com

Please download and install that, and then try again.


As for the plugin not really working if the dependency is not available, one option is to simple disable itself, eg.


EnablePlugin(GetPluginID (), false)


Then callbacks won't be called, and triggers and aliases are disabled.

A problem with that still is that the disabled plugin won't process OnPluginListChanged still (when the dependency becomes available).

Another option is to put all triggers etc. into a group, and just disable the group.

Another option is to just check at applicable places if the dependency plugin is there, eg.


if my_service then
  my_service.DoSomethingHere (a, b, c)
end -- if plugin available


The last case could be useful if the plugin is not essential, for example, it plays sounds.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #18 on Fri 08 Jan 2010 08:48 PM (UTC)
Message
True, I like the idea of a Note() then disabling itself. I don't see the requirement of reinstalling the plugin afterward to be a huge deal, personally.

Putting all triggers, aliases, etc. into one group means that they can't be part of any other group. That's not really a good thing when you have sets triggers that depend on other triggers.

The "check if dependency, if so, do something" approach is probably best for soft dependencies, where the plugin can keep working if its dependency isn't loaded. No complaints here.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #19 on Sat 09 Jan 2010 04:12 AM (UTC)
Message
I'm proud to release PPI v1.1.0, with one minor fix and two additions to make it easier and more natural to use. [1]

One change is the addition of a SUPPORTS internal message, which queries the service about the existence of a particular message. This is utilized in __index, so when you ask for a nonexistant function, it returns nil, just like it would natively.

Second is the addition of (nil, "error string") return values to PPI.Load(). It first checks to see if the plugin is installed (if not, it returns nil, "not_installed"), then it checks if the plugin supports PPI (if not, it returns nil, "no_ppi").


Also, Nick, I noticed you amended the 4.46 release notes to show that you added your derived version of PPI to the standard distribution! I'm honored that my work was found to be so useful. I do feel like pointing out, though, that this latest version (and indeed the one before, IIRC) supports deeper serialization, where a table can refer to other tables or even itself. I also think that this version is perhaps a bit less limiting, as yours (I assume) seems to reserve keys beginning with _, like _id and _version.

Incidentally, I'm considering adding new functionality that would support directly accessing (but not modifying) exposed values like strings, numbers, booleans, and tables, as well as what we already have, methods. I may also be able to add "serialization" functionality for passed/returned methods, by storing them in another section of the private data of the PPI, passing an internal name/value referring to it, and on the other side creating an internal thunk that is passed back to the user.

[1] http://mushclient.pastebin.com/f75922737

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #20 on Sat 09 Jan 2010 05:00 AM (UTC)

Amended on Sat 09 Jan 2010 05:01 AM (UTC) by Nick Gammon

Message
I think my version has the benefit of simplicity, whereas yours has the benefit of greater power. Perhaps both can be part of the next release, although we need a new name for one of them.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #21 on Sat 09 Jan 2010 05:14 AM (UTC)
Message
I don't know... they're both very simple on the user side of things. They have the exact same user interface, at any rate. It's just how things are done internally that's different, and lends it greater power. And it's how things appear to the user that's important, right? Mine also does things in a certain way to make it feel as native as possible, like checking if a method is supported and returning nil if not, which I don't think yours does.

It seems kind of strange to provide two libraries that do the same thing, only one is named differently and one does things in a better and more flexible (IMHO) way.

Of course, if you have any suggestions for simplifying my library's design, I'm all for hearing them! I always try to make my work as clean and complete as possible.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #22 on Sat 09 Jan 2010 07:39 PM (UTC)
Message
OK, well for the record, here is my proposed release version:


--[[

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


I liked your idea of incorporating the PPI version, so I added that. Changes you might consider are:


  • The "module" line hides all local variables from the rest of the script (the calling script) so there aren't any possible clashes between module variables and script variables.

  • In the Load function, if the plugin does not exist, I return nil followed by an error message. This makes Load suitable to assert, eg.

    
    my_service = assert (ppi.Load "<something>")
    


    Of course, this goes against what I was saying about a helpful error message, but if you are going to just do an error anyway, this lets you do it in a single line.

  • In the table returned by Load I use GetPluginInfo to return various useful variables, like the plugin's name and version. The version helps for version checks of the plugin, and the name helps in doing error messages.

  • I didn't bother cleaning up the returned value variables, on the grounds that this is an overhead, and you will probably be doing it thousands of times (creating the variable / deleting it) if the service is used a lot.


Between us we are hopefully moving towards something truly elegant and useful. :-)

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #23 on Sat 09 Jan 2010 08:12 PM (UTC)

Amended on Sat 09 Jan 2010 08:21 PM (UTC) by Twisol

Message
Actually, module() isn't needed to hide local variables. A file is loaded and then executed as though it were a function, just like loadstring("script")(). All module() does is set the environment of the currently executing frame - that would be the file - to a new table, which is also set into packages.loaded[module_name_here]. It doesn't actually do anything to local values, precisely because they are local to that frame anyways.

With Load, I also return nil followed by an error message, though my messages are shorter and more useful to the calling script rather than the user (which makes sense, because the plugin will want to give its own error message).

In regards to the PPI returned from PPI.Load, I have to admit that it makes me uncomfortable to be imposing restrictions on what names the service can expose. As you've shown, most of that information can be easily accessed through GetPluginInfo, and most of it (directory, required_version) won't be used very often at all. 'enabled' is even worse, in my opinion, as it isn't updated automatically when the plugin is disabled. The PPI version numbers, well... to be honest, I have no idea why you're including them in each individual PPI rather than the package table itself. It might make sense to store the service's PPI version, but not the client's.

On reflection, I agree with the cleanup overhead point to a certain extent. However, I ran a test loop to check how long it takes to execute 20,000 total SetVariable() and DeleteVariable() calls (that's 10,000 of each), and it took less than a second. I don't think we have to worry about it too much, and I do prefer to keep things tidy.


Writing the previously-mentioned function-passing and direct indexing of exposed values is turning out to be a little difficult, but mostly because of assumptions I had made at the beginning. It will probably take a little longer, but I'm cleaning up as I go anyways, so it will be better in the long run.

I must agree, this is a very fun project and I think it is astonishingly useful already. Could I ask if you're planning on releasing 4.46 soon? I'd like to finish these last additions beforehand if I have enough time.


EDIT: In regards to module() again, you're right in that it would hide the serialize library, since that sets its package into _G. A benefit of mine, though, is that the serialization routines are part of the PPI script already, so only exactly what I want is exposed, and no more.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #24 on Sat 09 Jan 2010 08:45 PM (UTC)
Message
Twisol said:

Could I ask if you're planning on releasing 4.46 soon? I'd like to finish these last additions beforehand if I have enough time.


Don't panic, I'll wait until all this is stabilized.


Twisol said:

EDIT: In regards to module() again, you're right in that it would hide the serialize library, since that sets its package into _G. A benefit of mine, though, is that the serialization routines are part of the PPI script already, so only exactly what I want is exposed, and no more.


I'm not sure if this will clash if someone requires "serialize" for serializing their normal variables. Why not make them local variables? And why not, again, use the module function? I can't see a major objection to it.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #25 on Sat 09 Jan 2010 08:46 PM (UTC)
Message
Twisol said:

With Load, I also return nil followed by an error message, though my messages are shorter and more useful to the calling script rather than the user (which makes sense, because the plugin will want to give its own error message).


Not in the version posted earlier on this page.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #26 on Sat 09 Jan 2010 08:52 PM (UTC)

Amended on Sat 09 Jan 2010 08:53 PM (UTC) by Twisol

Message
No, they are loca... well, wait, they're not. Sorry, that was an oversight from when I was testing them. They're local now. *looks around shiftily*

I don't like using module() for two reasons. One, if I don't use package.seeall, I have to save every outside function I want as a local before calling module(). Two, if I do use package.seeall, all of _G is put into the package table. There's a lengthy critique of module() on the Lua-Users wiki, and more often than not I find that I can manage well enough on my own. (You ask why not use module(), I say why use module()? Two different standpoints ;) )


Nick Gammon said:
Don't panic, I'll wait until all this is stabilized.

Thanks!

Nick Gammon said:

Twisol said:

With Load, I also return nil followed by an error message, though my messages are shorter and more useful to the calling script rather than the user (which makes sense, because the plugin will want to give its own error message).


Not in the version posted earlier on this page.

Ah, well, it's in my working revision. *cough*

[1] http://lua-users.org/wiki/LuaModuleFunctionCritiqued

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #27 on Sat 09 Jan 2010 09:16 PM (UTC)
Message
Twisol said:

Two, if I do use package.seeall, all of _G is put into the package table.


No it isn't, "all of _G" is not put anywhere. All it does is put an __index entry in the package, pointing to the _G table. So, it *finds* the global variables, but can't change them. This is actually safer, you don't accidentally corrupt something in _G (like a counter or something). Here is the relevant code in Lua:


static int ll_seeall (lua_State *L) {
  luaL_checktype(L, 1, LUA_TTABLE);
  if (!lua_getmetatable(L, 1)) {
    lua_createtable(L, 0, 1); /* create new metatable */
    lua_pushvalue(L, -1);
    lua_setmetatable(L, 1);
  }
  lua_pushvalue(L, LUA_GLOBALSINDEX);
  lua_setfield(L, -2, "__index");  /* mt.__index = _G */
  return 0;
}


Admittedly, there is an overhead of the indirect lookup via the metatable, but then you are doing the exact same thing yourself in the module (using __index) so it can't be *too* bad. :P

If you were worried, for speed purposes, you would indeed copy all the relevant global functions into local variables. This would be faster again than your current approach, because using global variables (eg. CallPlugin, SetVariable) involves looking them up in the _G table every time.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #28 on Sat 09 Jan 2010 09:44 PM (UTC)
Message
I meant that it was accessible through the package table, my apologies for my loose wording.

local myfile = [[
  module("test", package.seeall)
  function MYFOO()
    print("Hi.")
  end
]]

loadstring(myfile)()


tprint(test)
"MYFOO"=function: 03083340
"_M":
  "MYFOO"=function: 03083340
  "_M"=table: 041A55A8
  "_NAME"="test"
  "_PACKAGE"=""
"_NAME"="test"
"_PACKAGE"=""


tprint(getmetatable(test))
...
everything
...
"package":
    "preload":
    "loadlib"=function: 02F7A858
    "loaded":
      all other packages
      ...
      including this package
...



It's not so much the overhead of the lookups as it is the weirdness of it. Suffice to say that I like to have control over what my module does, and how.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #29 on Sat 09 Jan 2010 10:35 PM (UTC)

Amended on Sat 09 Jan 2010 10:38 PM (UTC) by Nick Gammon

Message
Well, back to exposing each function individually. I thought, "why not let Lua do it for us?". So a quick bit of code later, and you can find all the functions that need to be made local, like this:


local script = [====[
--[[

<< put your function code here >>

]====]

local found = {}
local libs_used = {}

local funcs = {}
local libraries = {
      "string",
      "package",
      "os",
      "io",
      "bc",
      "progress",
      "bit",
      "rex",
      "utils",
      "table",
      "math",
      "debug",
      "coroutine",
      "lpeg",
      "sqlite3",
      "world",
  } -- end of libraries

local tables = {
      "trigger_flag",
      "alias_flag",
      "timer_flag",
      "custom_colour",
      "error_code",
      "sendto",
   }  -- end of tables

for k, v in pairs (_G) do
  if type (v) == "function" then
    funcs [k] = true
  end -- if 
end -- for

for _, lib in ipairs (libraries) do
  for k, v in pairs (_G [lib]) do
    if type (v) == "function" then
      funcs [lib .. "." .. k] = true
      if lib == "world" then
        funcs [k] = true
      end -- if
    end -- if 
  end -- for

end -- for each library

-- now do stuff like error_code, colours etc.

for _, lib in ipairs (tables) do
  for k, v in pairs (_G [lib]) do
      funcs [lib .. "." .. k] = true
  end -- for
end -- for each table

-- scan our script for matching functions

for w in string.gmatch (script, "[%a%d._]+") do
  if funcs [w] then
    local lib, func = string.match (w, "^([%a%d_]+)%.([%a%d_]+)")
    if lib then
      libs_used [lib] = libs_used [lib] or {}
      libs_used [lib] [func] = true
    else
      found [w] = true
    end -- if
  end -- if  

end -- for

local tl = {}
for k in pairs (libs_used) do
  table.insert (tl, k)
end -- for

table.sort (tl)

for _, lib in ipairs (tl) do
  print ("local", lib, "= {")
  for k in pairs (libs_used [lib]) do
    print ("  ", k, "=", lib .. "." .. k .. ",")
  end -- for
  print ("  }")
end -- for

local t = {}
for k in pairs (found) do
  table.insert (t, k)
end -- for

table.insert (t, "_G")

table.sort (t)

print ("local", table.concat (t, ", "), "=", table.concat (t, ", "))


Now the output from that in the case of my earlier PPI module was this:


local error_code = {
eOK = error_code.eOK,
}
local package = {
seeall = package.seeall,
}
local string = {
format = string.format,
}
local CallPlugin, ColourNote, GetPluginInfo, GetPluginName, GetPluginVariable, IsPluginInstalled, Note, SetVariable, _G, assert, check, getfenv, load, loadstring, module, print, require, setfenv, setmetatable, type, unpack = CallPlugin, ColourNote, GetPluginInfo, GetPluginName, GetPluginVariable, IsPluginInstalled, Note, SetVariable, _G, assert, check, getfenv, load, loadstring, module, print, require, setfenv, setmetatable, type, unpack


Now you follow that by:


-- hide all except non-local variables
module (...)


And you are all set! It isn't perfect, in particular I assumed my coding style where I don't put a space between things like: string . format

Also, it doesn't notice comments, so if a function appeared only in a comment, it would still be entered in the list.


However it is an interesting illustration of using Lua to parse some Lua source and pull out useful things.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


276,841 views.

This is page 2, subject is 10 pages long:  [Previous page]  1  2 3  4  5  6  7  8  9  10  [Next page]

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]