Problems with OnPluginConnect

Posted by Twisol on Mon 20 Apr 2009 07:23 AM — 19 posts, 70,607 views.

USA #0
I'm having some rather odd problems, and I'm not sure what's going on. I'm using my ATCP plugin, which broadcasts to other plugins to ask if they need any modules enabled, then sends the resulting 'hello' ATCP message to the server. I have four plugins that use this functionality currently, each requesting one module: room_brief, char_vitals, and two requests for room_exits.

When I load up MUSHclient and connect, the message sends for char_vitals and room_brief, but not room_exits. Disabling one of the room_exits plugins fixes this problem, but I'm not sure why. (The code that sets the modules is located in EnableModule())

The second problem that I'm experiencing is, when I disconnect and then reconnect, sometimes (pretty frequently) no modules are enabled even though I know the broadcast is being responded to. I have -no- idea what's causing this.

Help would be majorly appreciated! =/

MUSHclient version is 4.40.


codes = {
  ["IAC WILL ATCP"] = "\255\251\200", -- server-sent
  ["IAC WONT ATCP"] = "\255\252\200", -- server-sent
  ["IAC DO ATCP"]   = "\255\253\200", -- client-sent, enables ATCP
  ["IAC SB ATCP"]   = "\255\250\200", -- begins an ATCP packet
  ["IAC SE"]        = "\255\240",     -- ends an ATCP packet
  ["IAC WILL EOR"]  = "\255\251\025", -- ???
}

client_id =  "MUSHclient " .. Version()
nexus_opts = {}
commands = {}

mods = {auth        = {version = "1"},
        char_name   = {version = "1"},
        char_vitals = {version = "1"},
        room_brief  = {version = "1"},
        room_exits  = {version = "1"},
        map_display = {version = "1"},
        composer    = {version = "1"},
        keepalive   = {version = "1"},
        topvote     = {version = "1"},
        ping        = {version = "1"},
       }

OnPluginConnect = function()
  -- alert other plugins to EnableModule now.
  BroadcastPlugin(0, "")

  local msg = "hello " .. client_id .. "\10"
  local line = ""

  for k, v in pairs(nexus_opts) do
    line = line .. k .. ", "
    msg = msg .. k .. " " .. v .. "\10"
  end

  Note(line:sub(1, line:len() - 2))

  SendPkt(codes["IAC DO ATCP"] .. codes["IAC SB ATCP"] .. msg .. codes["IAC SE"])
end

OnPluginDisconnect = function()
  nexus_opts = {}
  commands = {}
end

OnPluginPacketReceived = function(packet)
  if string.find (packet, codes["IAC WILL ATCP"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WILL ATCP"] .. "(.-)", "%1%2")
  end
  if string.find (packet, codes["IAC WONT ATCP"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WONT ATCP"] .. "(.-)", "%1%2")
  end
  if string.find (packet, codes["IAC WILL EOR"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WILL EOR"] .. "(.-)", "%1%2")
  end

  local packet, atcp, parsed = parseATCP(packet)
  -- pack up the messages and send them to listeners
  for msg, line in pairs(parsed) do
    BroadcastPlugin(1, msg .. "|" .. line)
  end

  return packet
end

-- splits an ATCP message into header and content lines
split_atcp = function(msg)
  -- find a Something.Something marker
  -- (i.e. Room.Brief, Char.Vitals)
                -- match1
  local start, en, head = string.find(msg, "^(%w+%.%w+)")
                                   -- PCRE: ^(\w+\.\w+)

  local cont = string.sub(msg, en+2)

  -- make sure we got anything at all
  if start then
    return head, cont
  else
  -- was a dud message (nothing between the begin/end bytes)
    return nil
  end
end

leftovers = ""
PAT = codes["IAC SB ATCP"] .. "(.-)" .. codes["IAC SE"]

parseATCP = function(packet)
  -- tacks on any remainder from the last packet
  local packet = leftovers .. packet
  leftovers = ""

  local atcp, parsed = {}, {}

  -- passed to string.gsub() below
  -- used to handle each capture
  local replace_func = function(cap)
    head, cont = split_atcp(cap, parsed)

    -- make sure we got something
    if head ~= nil then
      parsed[head] = cont
      table.insert(atcp, cap)
    end
    return ""
  end

  -- catch atcp messages, remove inline
  packet = string.gsub (packet, PAT, replace_func)

  -- finds any leftovers from an unfinished ATCP message
  local s,e,msg = string.find (packet, "(\255[^\255]-\255?)$")
  if s then
    if (#msg == 1) or ( (#msg > 2) and string.find (msg, "^\255\250\200") ) 
       or ( (#msg > 1) and string.find (msg, "^\255\250") ) then 
      leftovers = msg
      packet = string.sub (packet, 1, s-1)
    end
  end
  return packet, atcp, parsed
end


EnableModule = function(lines)
  if type(lines) == "string" then
    lines = utils.split(lines, ",")
  elseif type(lines) ~= "table" then
    error("Invalid argument to EnableModule: " .. lines .. "\10")
  end
  -- lines should be a table at this point

  for _, m in ipairs(lines) do
    Note(m)

    v = mods[m]
    if v then
      nexus_opts[m] = v.version

      -- import module commands
      v.version = nil
      for i, j in pairs(v) do
        Note("Loading command " .. i .. ": " .. j)
        commands[i] = j
      end
    end
  end
end
Amended on Mon 20 Apr 2009 04:17 PM by David Haley
USA #1
(I took the liberty of fixing the forum code in your post to prevent the subscripting from italicizing the rest of the program.)
USA #2
Thanks <_<
Australia Forum Administrator #3
This doesn't look right to me:


if type(lines) == "string" then
    lines = utils.split(lines, ",")
  elseif type(lines) ~= "table" then
    error("Invalid argument to EnableModule: " .. lines .. "\10")
  end
  -- lines should be a table at this point


This is the function that is called back from the BroadcastPlugin is that it?

You know, with elseif, if the first condition matches it won't do the second test? If you are expecting to break up the incoming line into a table, and then check you have a table, you need two if statements, like this:


if type(lines) == "string" then
    lines = utils.split(lines, ",")
end

if type(lines) ~= "table" then
    error("Invalid argument to EnableModule: " .. lines .. "\10")
end

 -- lines should be a table at this point
Australia Forum Administrator #4
Also, this works, but is a different style to what I use elsewhere:


EnableModule = function(lines)
  -- blah blah
end -- function EnableModule


I prefer the syntactic sugar that makes it clearer we are starting a function definition:


function EnableModule (lines)
  -- blah blah
end -- function EnableModule


For me, the keyword "function" stands out more at the start of the line.
Australia Forum Administrator #5
Quote:

["IAC WILL EOR"] = "\255\251\025", -- ???


See: http://www.faqs.org/rfcs/rfc885.html

This is the END-OF-RECORD option.
Australia Forum Administrator #6
Quote:

error("Invalid argument to EnableModule: " .. lines .. "\10")



BTW, \10 is the newline character, so it is more usual to use \n.

In any case I don't think you need it, error messages are not normally terminated by a newline, Lua adds that anyway.
Australia Forum Administrator #7
Quote:

if type(lines) ~= "table" then
error("Invalid argument to EnableModule: " .. lines .. "\10")
end


In fact, this is a good use for assert, ie.


assert (type (lines) == "table", "Invalid argument to EnableModule: " .. lines)

USA #8
Whew, lots of replies.

Quote:

if type(lines) == "string" then
lines = utils.split(lines, ",")
elseif type(lines) ~= "table" then
error("Invalid argument to EnableModule: " .. lines .. "\10")
end
-- lines should be a table at this point

The argument should come in as either a string or a table. If it's a string, I convert it to a table. I don't need to check that, because I know it's already a table. If it's not a string, it checks the elseif. If it's not a table already, I don't care what it is, it's wrong! ;)

Quote:

This is the END-OF-RECORD option.

Ah, thanks. Didn't particularly know what it was for or why I needed to watch for it in the packets, since I based this plugin off of another.


Quote:

BTW, \10 is the newline character, so it is more usual to use \n.

In any case I don't think you need it, error messages are not normally terminated by a newline, Lua adds that anyway.

I've had strange experiences with needing arbitrary amounts of slashes before the \n, and when I used \10 at some random point in time it worked, so it became a habit. Not technically the best way to start a habit, I suppose.




Anyways, I still haven't discovered why it's not working depending on how many times I've called OnPluginConnect. It only seems to work the first time I open the world and connect, which implies that something's up with my 'mods' table, and I'm printing the contents of 'mods' every time I connect, so nothing's off there...
Australia Forum Administrator #9
Personally I think this is confusing:


  local packet, atcp, parsed = parseATCP(packet)


You are redefining packet here (creating a new local variable of the same name). I think it is OK to do it, but it is confusing.

Quote:

... no modules are enabled even though I know the broadcast is being responded to ...


What do you mean by that exactly? What variable/table is being set or not set, and how do you know this?
Australia Forum Administrator #10
Quote:

I've had strange experiences with needing arbitrary amounts of slashes before the \n, and when I used \10 at some random point in time it worked, so it became a habit.


If you use send-to-script, then \n gets expanded to a newline before it hits the script engine, and thus may cause problems (eg. unterminated literal strings). However \10 would not be have so expanded.

But when you move your scripts to a script file, then this pre-expansion is not done, and you can just code in the normal way.
USA #11
Quote:
You are redefining packet here (creating a new local variable of the same name). I think it is OK to do it, but it is confusing.

You're probably right, though that code was there already. I think the idea is that we're replacing the old packet with the ATCP-less packet.


Quote:
What do you mean by that exactly? What variable/table is being set or not set, and how do you know this?

I checked nexus_opts after BroadcastPlugin(0, "") and it was empty, and I also put a Note() in OnBroadcastPlugin of one of my ATCP-using plugins that shows that, yes, it is indeed receiving the broadcast (and responding). I also put Note()s in EnableModule to check what was going on in there.
Australia Forum Administrator #12
Some test plugins I just wrote seem to indicate that the BroadcastPlugin and CallPlugin functions work exactly as I expected, even if you disconnect and reconnect.

Exactly what are your debugging displays showing? Is EnableModule doing what it should and putting things into the nexus_opts table? If so, I don't see why it should be empty afterwards.
Australia Forum Administrator #13
Is your plugin exactly what you posted here? If you start adding "local" here and there it can completely change how it will work.
USA #14
Word for word. Given, I removed some comments and functions that weren't even used at all (SendATCP and the mods.<module>.<command> functions; none of my plugins use those), but apart from that I didn't add any extra 'local's or anything. The only reason I removed stuff at all was to fit the X000-character limit on posts.
Amended on Tue 21 Apr 2009 07:38 AM by Twisol
Australia Forum Administrator #15
And what are your debugging displays showing?
USA #16
I discovered the problem, by adding one more Note() in a key location. :P I'm setting 'v.version' to nil in EnableModule, but apparently 'v' really references 'mods[m]', and is not actually a copy, so by unsetting v.version I'm actually killing it the next time I try to set that module, whether during the same connection or during a later one. >_<

Thanks for the help, heh. It wasn't a very obvious problem I guess.
Australia Forum Administrator #17
Yes, a bit tricky. :)


v = mods[m]
    if v then
      nexus_opts[m] = v.version


v is a table, and tables are passed by reference, not by value.
USA #18
Right. I fixed it by changing it to "if v.version then", also replacing the set-to-nil with an "if i ~= "version" in the inner loop.