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

Gammon Forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  General
. . -> [Subject]  Plugins and variables
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Plugins and variables

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


Posted by Indoum   Sweden  (17 posts)  [Biography] bio
Date Thu 06 Apr 2006 06:04 PM (UTC)
Message
I've been struggling with finding a way to have plugins save my configuration varibles in my worldfile, using various methods (since SetVarible() doesn't seem to work from plugins). The method I've settled with for now is to have all config-changes register themselves by doing Execute("_cfg <name> <value>") and then have an alias in my world-file pick it up and serialize it into a varible. The file with the _cfg-alias is also loaded into my plugins, to have them synchronise their values. However, that is only use to a limit. Now I have need for tables within the config-table, however much I try to avoid it (because I really like to have the entire system in plugins).

If this makes any sense to you - how am I to do this?
[Go to top] top

Posted by Nick Gammon   Australia  (21,386 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Thu 06 Apr 2006 10:48 PM (UTC)

Amended on Thu 06 Apr 2006 10:50 PM (UTC) by Nick Gammon

Message
I'm not sure I understand the problem here. Plugins are designed to save their internal variables into a plugin "state" file which is uniquely named for each plugin/world combination.

You need to have "save_state" set to "y" in the plugin header. An example plugin that does that is the Random_Socials plugin. Its header reads:


<plugin name="Random_Socials"
  author="Nick Gammon"
  language="vbscript"
  id = "982581e59ab42844527eec80"
  purpose = "Displays a random social from time to time"
  save_state = "y"
  version = "1.1"
  >


Once you have set that flag any variables saved last time are automatically reloaded when you load the plugin next time. It is all fully automatic.

As for tables, if you are using Lua then you will need to serialize from Lua variables into MUSHclient variables and back again, at plugin load/save time.

Read this forum post:

http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=4960

It describes how you can serialize Lua variables into and out of MUSHclient variables. Then see:

http://www.gammon.com.au/scripts/doc.php?general=plugin_callbacks

You can use OnPluginInstall to serialize from MUSHclient variables to Lua variables, and then OnPluginSaveState for saving them.

- Nick Gammon

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

Posted by Indoum   Sweden  (17 posts)  [Biography] bio
Date Reply #2 on Thu 06 Apr 2006 11:53 PM (UTC)

Amended on Thu 06 Apr 2006 11:54 PM (UTC) by Indoum

Message
I still get the same problem I've always had when trying to convert to using only plugins, it just wont save. Tests shows it serialize just fine, it's just as if it wont react to the callback (OnPluginSave) or it wont write to the worldfile. Either way, when I shut down MUSHclient the variables are gone.

Currently I have:
-- LOAD VARIABLES
_cfg, _var = {}, {}
if GetPluginVariable("", "skoid_cfg") then loadstring(GetPluginVariable("", "skoid_cfg"))() end
if GetPluginVariable("", "skoid_var") then loadstring(GetPluginVariable("", "skoid_var"))() end

-- SAVE VARIABLES
function OnPluginSaveState()
    SetVariable("skoid_cfg", serialize("_cfg"))
    SetVariable("skoid_var", serialize("_var"))
end


I created some temporary aliases to toy around with this:
<aliases>
    <alias enabled="y" keep_evaluating="y" match="^cfg$" regexp="y" send_to="12">
        <send>Note(cfg)</send>
    </alias>
    <alias enabled="y" keep_evaluating="y" match="^cfg2$" regexp="y" send_to="12">
        <send>Note(cfg.test)</send>
    </alias>
    <alias enabled="y" keep_evaluating="y" match="^cfg3$" regexp="y" send_to="12">
        <send>cfg.test = "heh"</send>
    </alias>
    <alias enabled="y" keep_evaluating="y" match="^cfg4$" regexp="y" send_to="12">
        <send>Note(_cfg.test)</send>
    </alias>
</aliases>


("cfg" is just a proxy for "_cfg")
It changes the varibles just as I want, but as soon as I shut the client down. Poof, gone.

And yeah, I have the save_state flag set to "y".
[Go to top] top

Posted by Nick Gammon   Australia  (21,386 posts)  [Biography] bio   Forum Administrator
Date Reply #3 on Fri 07 Apr 2006 12:30 AM (UTC)

Amended on Fri 07 Apr 2006 12:31 AM (UTC) by Nick Gammon

Message
Quote:

if GetPluginVariable("", "skoid_cfg") then loadstring(GetPluginVariable("", "skoid_cfg"))() end


This isn't right. From the documentation for GetPluginVariable:




If you want to find the value of a variable in the current plugin, use "GetVariable".

If you are writing a plugin and want to find a "global" MUSHclient variable value, use an empty plugin ID, eg.

world.Note world.GetPluginVariable ("", "target")




What your code is doing is pulling in the global variable, not the plugin's serialized variable.

- Nick Gammon

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

Posted by Indoum   Sweden  (17 posts)  [Biography] bio
Date Reply #4 on Fri 07 Apr 2006 12:43 AM (UTC)
Message
Did I tell you I love you? :D This' been dodging me for months, just couldn't figure it out. Thanks alot!
[Go to top] top

Posted by Nick Gammon   Australia  (21,386 posts)  [Biography] bio   Forum Administrator
Date Reply #5 on Fri 07 Apr 2006 12:57 AM (UTC)

Amended on Fri 07 Apr 2006 12:59 AM (UTC) by Nick Gammon

Message
While you were replying I made a test plugin to show the idea...

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Friday, April 07, 2006, 10:41 AM -->
<!-- MuClient version 3.74 -->

<!-- Plugin "serialize_test" generated by Plugin Wizard -->

<muclient>
<plugin
   name="serialize_test"
   author="Nick Gammon"
   id="22d87669195ffdc6f3b4104d"
   language="Lua"
   purpose="Tests serializing Lua variables"
   save_state="y"
   date_written="2006-04-07 10:38:38"
   requires="3.74"
   version="1.0"
   >

</plugin>


<!--  Aliases  -->

<aliases>
  <alias
   match="^cfg_show$"
   enabled="y"
   regexp="y"
   send_to="12"
   keep_evaluating="y"
  >
  <send>Note(cfg.test)</send>
  </alias>
  <alias
   match="^cfg_set (.+)$"
   enabled="y"
   regexp="y"
   send_to="12"
   keep_evaluating="y"
  >
  <send>cfg.test = "%1"</send>
  </alias>
</aliases>

<!--  Script  -->


<script>
<![CDATA[
-- ----------------------------------------------------------
-- serializer
-- See "Programming In Lua" chapter 12.1.2.
-- Also see forum thread:
--   http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=4960
-- ----------------------------------------------------------
function basicSerialize (o)
  if type(o) == "number" or type(o) == "boolean" then
    return tostring(o)
  else   -- assume it is a string
    return string.format("%q", o)
  end
end -- basicSerialize 

--
-- Lua keywords might look OK to not be quoted as keys but must be.
-- So, we make a list of them.
--

lua_reserved_words = {}

for _, v in pairs ({
    "and", "break", "do", "else", "elseif", "end", "false", 
    "for", "function", "if", "in", "local", "nil", "not", "or", 
    "repeat", "return", "then", "true", "until", "while"
            }) do lua_reserved_words [v] = true end

-- ----------------------------------------------------------
-- save one variable (calls itself recursively)
-- 
-- Modified on 23 October 2005 to better handle keys (like table keys)
-- ----------------------------------------------------------
function save (name, value, out, indent, saved)
  saved = saved or {}       -- initial value
  indent = indent or 0      -- start indenting at zero cols
  local iname = string.rep (" ", indent) .. name -- indented name

  -- numbers, strings, and booleans can be simply serialized

  if type(value) == "number" or 
     type(value) == "string" or
     type(value) == "boolean" then
    table.insert (out, iname .. " = " .. basicSerialize(value))

  -- tables need to be constructed, unless we have already done it

  elseif type(value) == "table" then
    if saved[value] then    -- value already saved?
      table.insert (out, iname .. " = " .. saved[value])  -- use its previous name
    else

  -- remember we have created this table so we don't do it twice

      saved[value] = name   -- save name for next time

  -- make the table constructor, and recurse to save its contents

      table.insert (out, iname .. " = {}")   -- create a new table
      local k, v
      for k, v in pairs (value) do      -- save its fields
        local fieldname 

        -- if key is a Lua variable name which is not a reserved word
        -- we can express it as tablename.keyname

        if type (k) == "string"
           and string.find (k, "^[_%a][_%a%d]*$") 
           and not lua_reserved_words [k] then
          fieldname = string.format("%s.%s", name, k)

        -- if key is a table itself, and we know its name then we can use that
        --  eg. tablename [ tablekeyname ]

        elseif type (k) == "table" and saved[k] then
          fieldname = string.format("%s[%s]", name, saved [k]) 

        -- if key is an unknown table, we have to raise an error as we cannot
        -- deduce its name
 
        elseif type (k) == "table" then
          error ("Key table entry " .. tostring (k) .. 
                 " in table " .. name .. " is not known")

        -- if key is a number or a boolean it can simply go in brackets,
        -- like this:  tablename [5] or tablename [true]

        elseif type (k) == "number" or type (k) == "boolean" then
          fieldname = string.format("%s[%s]", name, tostring (k))

        -- now key should be a string, otherwise an error
 
        elseif type (k) ~= "string" then
          error ("Cannot serialize table keys of type '" .. type (k) ..
                 "' in table " .. name)

        -- if key is a non-variable name (eg. "++") then we have to put it
        -- in brackets and quote it, like this:  tablename ["keyname"]

        else
          fieldname  = string.format("%s[%s]", name,
                                        basicSerialize(k))  
        end

        -- now we have finally worked out a suitable name for the key,
        -- recurse to save the value associated with it

        save(fieldname, v, out, indent + 2, saved) 
      end
    end

  -- cannot serialize things like functions, threads

  else
    error("Cannot save a " .. type(value))
  end
end  -- save 

-- ----------------------------------------------------------
-- Serialize a variable or nested set of tables:
-- ----------------------------------------------------------

--[[

  Example of use:

  SetVariable ("mobs", serialize ("mobs"))  --> serialize mobs table
  loadstring (GetVariable ("mobs")) ()  --> restore mobs table 

  If you need to serialize two tables where subsequent ones refer to earlier ones
  you can supply your own "saved tables" variable, like this:

    t = {}
    result = serialize ("mobs", nil, t)
    result = result .. "\n" .. serialize ("quests", nil, t)

  In this example the serializing of "quests" also knows about the "mobs" table
  and will use references to it where necessary.  

  You can also supply the actual variable if the variable to be serialized does
  not exist in the global namespace (for instance, if the variable is a local 
  variable to a function). eg.

    do
      local myvar = { 1, 2, 8, 9 }
      print (serialize ("myvar", myvar))
    end

  In this example, without supplying the location of "myvar" the serialize would fail
  because it would not be found in the _G namespace.

--]]

function serialize (what, v, saved)
  v = v or _G [what]  -- default to "what" in global namespace

  assert (type (what) == "string", 
          "1st argument to serialize should be the *name* of a variable")
  
  assert (v, "Variable '" .. what .. "' does not exist")

  assert (type (saved) == "table" or type (saved) == "nil", 
          "3rd argument to serialize should be a table or nil")

  local out = {}  -- output to this table
  save (what, v, out, nil, saved)   -- do serialization
  return table.concat (out, "\r\n")  -- turn into a string
end -- serialize

cfg = {}

-- SAVE VARIABLES
function OnPluginSaveState ()
  SetVariable("skoid_cfg", serialize("cfg"))
end -- OnPluginSaveState

function OnPluginInstall ()
  loadstring (GetVariable("skoid_cfg") or "") ()
end -- OnPluginInstall 
]]>
</script>


</muclient>



This seems to work fine. To prove things are changing I modified the alias, so I can type something like:


cfg_set nick


Now if you reinstall the plugin (which forces it to close and re-open), the state file now looks like this:


<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Friday, April 07, 2006, 10:51 AM -->
<!-- MuClient version 3.74 -->
<!-- Written by Nick Gammon -->
<!-- Home Page: http://www.muclient.com/ -->

<!-- Plugin state saved. Plugin: "serialize_test". World: "SMAUG". -->

<muclient>

<!-- variables -->

<variables
   muclient_version="3.74"
   world_file_version="15"
   date_saved="2006-04-07 10:51:59"
  >
  <variable name="skoid_cfg">cfg = {}
  cfg.test = &quot;nick&quot;</variable>
</variables>
</muclient>



As you can see the variable is set, and when you do a cfg_show, it displays "nick".

Note the use of OnPluginInstall for loading the variable, and:


GetVariable("skoid_cfg") or ""


This handles the case of the variable not existing yet.

- 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.


3,868 views.

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 FutureQuest]