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


Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to "verify" your details, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
[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 Fadedparadox   USA  (91 posts)  [Biography] bio
Date Reply #105 on Fri 22 Jan 2010 06:58 PM (UTC)
Message
David Haley said:

To be clear, I was talking not about using them in general, but needing to communicate them across plugins. Besides, these are just another form of anonymous function; there's nothing particularly different about them being in a table or not.

Ah, I misunderstood. As long as the function is accessible, where the function was originally in a table, I'll be alright with it. And for my uses, it wouldn't matter if I recieved a copy of the function, or access to the function in the original plugin.
[Go to top] top

Posted by Fadedparadox   USA  (91 posts)  [Biography] bio
Date Reply #106 on Fri 22 Jan 2010 07:01 PM (UTC)

Amended on Fri 22 Jan 2010 07:18 PM (UTC) by Fadedparadox

Message
David Haley said:

I still need a use case, though.


I'm not sure if this is what you mean, but this is a function I'd make accessible to additional plugins.


rift = {
  -- stuff cut out before
  display = function (spec)
    if spec then string.lower (spec) end
    spec = rift.long[spec] or spec
    AnsiNote (ansicolor (2), "Your rift contains:")
    for k, v in ipairs (rift.cats) do
      if (spec == v) or ((not spec) and rift.groups[v]) then
        AnsiNote (ansicolor (12), "\n" .. string.proper (rift.short[v] or v))
        rift.displaygroup (v)
      end -- if
    end -- for
    prompt.draw ()
    rift.min = nil
  end, -- func
  -- stuff cut out after
  }


I'd obviously change it to work more generally, but most of the function would remain how it is.
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #107 on Fri 22 Jan 2010 07:34 PM (UTC)
Message
Twisol said:
I also gave you a clear use case, which has implications stretching far beyond the simple (yet still very useful) example involving hooking into a button click.

Not really. You said (basically) "here's a case where we can stick functions inside tables". What I want is a case where it really doesn't make any sense to do otherwise. (Coming up with situations where we "can" do this-or-that is easy and uninteresting.)

Fadedparadox said:
I'm not sure if this is what you mean, but this is a function I'd make accessible to additional plugins.

Maybe it's that I don't understand the bigger picture here, but I don't see why this has to be a function inside of a table as opposed to a function invoked with the table as an argument.

To be clear here again, the point of my comments is not that it's dumb to put functions in tables or anything like that. In fact, it makes a huge amount of sense for all kinds of reasons. My issue is with what should be done at the cross-plugin interface.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #108 on Fri 22 Jan 2010 07:45 PM (UTC)
Message
Alright. It doesn't make sense, or at least is less clear and more typing, to create a singular method for every operation you can execute on a widget and require some ID for the widget you want to be modified to be passed to each one. Strictly speaking, my use-case makes it feasible and intuitive to give object proxies to a user. This is absolutely a case when you -should- stick functions inside tables.

Some theoretical example code (only theoretical because I haven't written it yet)
dialog = plugin.GetWidget("dialog")

ok = dialog.GetChild("button_ok")
cancel = dialog.GetChild("button_cancel")

function ButtonOK()
  print("You clicked OK")
  dialog.Destroy()
end

function ButtonCancel()
  print("You clicked Cancel")
  dialog.Destroy()
end

ok.Bind("mouseup", ButtonOK)
cancel.Bind("mouseup", ButtonCancel)



David Haley said:
To be clear here again, the point of my comments is not that it's dumb to put functions in tables or anything like that. In fact, it makes a huge amount of sense for all kinds of reasons. My issue is with what should be done at the cross-plugin interface.


To me, the interface should be able to deal with whatever personal preference the user might have. Some people like to put things in tables out of habit, as a namespacing mechanism. Other times you might have a resource, and construct a proxy object with some methods, and make the resource available via an upvalue. It just makes things so much easier and clearer to have these options, rather than be restricted to non-local, non-tabled, non-closured (i.e. just vanilla global) functions.

'Soludra' on Achaea

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

Posted by Nick Gammon   Australia  (23,042 posts)  [Biography] bio   Forum Administrator
Date Reply #109 on Fri 22 Jan 2010 07:57 PM (UTC)
Message
Fadedparadox said:

I'm not sure if this is what you mean, but this is a function I'd make accessible to additional plugins. ...


I think the whole idea of the PPI script is to make functions available to additional plugins. However what I think David and Twisol are arguing about is *passing* functions to plugins, as arguments to another function. Maybe I'm wrong about that.

As a real-world example, say you have a status-bar plugin, and on the status bar you want to show if you are in a battle or not (eg. draw the border in red). However the status-bar plugin does not know if it is in a battle. So it calls a "battle" plugin asking to be notified when battles stop or start.

eg.


function OnStartBattle ()
  DrawBorder "red"
end 

function OnStopBattle ()
  DrawBorder "black"
end 

-- call other plugin

battle_plugin.NotifyMeOfBattles (OnStartBattle, OnStopBattle)


Now the function NotifyMeOfBattles (in the other plugin) calls back into the first plugin at the start and the end of a battle. I can see the use of this.

However as I mentioned a few pages back, which seems to have been ignored, another way of doing that is simply to have the battle plugin do a BroadcastPlugin call to tell *all* plugins (whether or not they are interested) that a battle has stopped or started.

Or, using the notify paradigm, you can pass the *names* of callbacks, like this:


function OnStartBattle ()
  DrawBorder "red"
end 

function OnStopBattle ()
  DrawBorder "black"
end 

-- call other plugin

ppi.Expose ("OnStartBattle")
ppi.Expose ("OnStopBattle")


battle_plugin.NotifyMeOfBattles ("OnStartBattle", "OnStopBattle")


This second technique first exposes the callback functions as ones available to be called back into this function, and then calls them by name (effectively, passing a string rather than a function). This is much more language-neutral.

For that matter, BroadcastPlugin is language-neutral too.

- Nick Gammon

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

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #110 on Fri 22 Jan 2010 08:03 PM (UTC)
Message
Quote:
Alright. It doesn't make sense, or at least is less clear and more typing, to create a singular method for every operation you can execute on a widget and require some ID for the widget you want to be modified to be passed to each one. Strictly speaking, my use-case makes it feasible and intuitive to give object proxies to a user. This is absolutely a case when you -should- stick functions inside tables.

Some theoretical example code (only theoretical because I haven't written it yet)

I don't understand what this is trying to show me. I'm also not at all convinced that it makes sense to separate things by plugins as opposed to normal modules.

Anyhow, this doesn't seem unreasonable to me:


function ButtonOK()
  print("You clicked OK")
  plugin.DestroyWidget("dialog")
end

plugin.BindEvent(plugin.GetWidget("dialog", "button_ok"), "mouseup", "ButtonOK")

where GetWidget is a function that takes a list of names, and uses them as a sequence of children. So GetWidget("a", "b") would be getting the child "b" of widget "a".

It would need to be understood that the return value of GetWidget is something that the plugin can use as a widget identifier. (This is the plugin's responsibility, completely separate from PPI.)

This lets us accomplish the same thing with far simpler language constructs. No need for fancy metatables, making things callable, moving functions around, blablabla. And IMHO it would be difficult to argue that the syntax is bad, either.

Quote:
To me, the interface should be able to deal with whatever personal preference the user might have.

Go write some Perl code, or worse yet PHP, for a while, and see how happy you are with the idea that everybody's preferences, that every style of writing code should be accommodated. :-)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #111 on Fri 22 Jan 2010 08:14 PM (UTC)
Message
Again, it's not separation, it's collaboration. The core plugin can do stuff on its own, but third-party plugins can hook into it automatically and provide extra functionality.


No, that's not unreasonable, but I do find it limiting. You have to expose a method for every potential operation at the plugin level, when it makes more sense (to me) to provide a literal object that contains them for you. Nothing in PPI prevents you from doing it the way you showed, mind you, but I think mine is more natural. Everything can be accomplished with the basic building blocks, but the whole idea is to build those into something better and easier to use.

And nobody said anything about metatables. Unless you mean the PPI_meta table and __index? How else would we save the name (or in my case, get the function ID) and store it for later use?


As for Perl/PHP, yes, I've had some experience with those. This is nothing like that kind of "please everyone" thing. I'm being no more lenient (and only very slightly less lenient) than Lua itself. Lua-centric? Sure, a bit. But my lenience also makes it easier for other languages to interface with PPI with their own constructs, whatever they might be.

'Soludra' on Achaea

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

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #112 on Fri 22 Jan 2010 08:24 PM (UTC)
Message
Quote:
You have to expose a method for every potential operation at the plugin level, when it makes more sense (to me) to provide a literal object that contains them for you.

I find it more useful to use objective metrics, like:
(1) what is the "expressive power" of an interface (i.e., are there some things that you simply cannot do -- and this is functionality, not style)
(2) how difficult is this "expression" (number of API calls, number of characters, etc., are all proxies for this, although they should all be taken with a grain of salt)
(3) how many functions it takes under the hood to implement all the stuff ('complexity' of the library)

Arguing about what is "natural" is a never-ending discussion where we might as well ask who prefers vanilla over chocolate. :-/

Quote:
And nobody said anything about metatables. Unless you mean the PPI_meta table and __index? How else would we save the name (or in my case, get the function ID) and store it for later use?

Well, in your example, you indeed have no choice other than to set up metatables so that things like dialog.getChild("foo") will work. My example requires no metatables at all other than for "plugin", and even that can be easily removed.

Quote:
This is nothing like that kind of "please everyone" thing.

I don't understand what you meant then when you said that every user's personal preference should be handled by the interface.

Quote:
But my lenience also makes it easier for other languages to interface with PPI with their own constructs, whatever they might be.

What is your basis for making this claim? Have you looked at what cross-language communication entails, and how your solution makes things much easier than other proposals?

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #113 on Fri 22 Jan 2010 08:39 PM (UTC)
Message
Sorry, Nick, I missed this post on accident!

Nick Gammon said:
Fadedparadox said:

I'm not sure if this is what you mean, but this is a function I'd make accessible to additional plugins. ...


I think the whole idea of the PPI script is to make functions available to additional plugins. However what I think David and Twisol are arguing about is *passing* functions to plugins, as arguments to another function. Maybe I'm wrong about that.

I think it's more about passing functions in general; the pass-by-name suggestion David had unfortunately leaves out local functions, table functions, and closures. (Remember, all of these are represented by the same exact thing on the client side, it's the service that actually deals with the function call.)

Nick Gammon said:
However as I mentioned a few pages back, which seems to have been ignored, another way of doing that is simply to have the battle plugin do a BroadcastPlugin call to tell *all* plugins (whether or not they are interested) that a battle has stopped or started.

I did give my reasons for why I thought this way was better. One is that it allows you to map incoming messages directly to functions, which does away with the if-else ladders to check ID and message. Another is, of course, the inherent rich type-passing that PPI implements.

Nick Gammon said:
Or, using the notify paradigm, you can pass the *names* of callbacks, like this:


function OnStartBattle ()
  DrawBorder "red"
end 

function OnStopBattle ()
  DrawBorder "black"
end 

-- call other plugin

ppi.Expose ("OnStartBattle")
ppi.Expose ("OnStopBattle")


battle_plugin.NotifyMeOfBattles ("OnStartBattle", "OnStopBattle")


This second technique first exposes the callback functions as ones available to be called back into this function, and then calls them by name (effectively, passing a string rather than a function). This is much more language-neutral.

I don't agree with that, to be honest. As I mentioned, passing by name requires that the function be available through _G, which locks out locals, closures, and table entries. Passing identifiers, or messages if you prefer to call them that, is in fact no different than BroadcastPlugin. As BroadcastPlugin and OnPluginBroadcast can do it in any language, it should be quite possible to implement in any language. Again, they aren't functions precisely, they are operations or messages.

Nick Gammon said:
For that matter, BroadcastPlugin is language-neutral too.

Indeed - my PPI is quite similar except for the points I mentioned earlier in the post.

'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 #114 on Fri 22 Jan 2010 08:52 PM (UTC)

Amended on Fri 22 Jan 2010 08:53 PM (UTC) by Twisol

Message
David Haley said:
Quote:
You have to expose a method for every potential operation at the plugin level, when it makes more sense (to me) to provide a literal object that contains them for you.

I find it more useful to use objective metrics, like:
(1) what is the "expressive power" of an interface (i.e., are there some things that you simply cannot do -- and this is functionality, not style)
(2) how difficult is this "expression" (number of API calls, number of characters, etc., are all proxies for this, although they should all be taken with a grain of salt)
(3) how many functions it takes under the hood to implement all the stuff ('complexity' of the library)

Arguing about what is "natural" is a never-ending discussion where we might as well ask who prefers vanilla over chocolate. :-/

I go by feel and intuition, with a good helping of imaginative forethought/critical thinking. That probably says more than anything else about our disagreements.

David Haley said:
Quote:
And nobody said anything about metatables. Unless you mean the PPI_meta table and __index? How else would we save the name (or in my case, get the function ID) and store it for later use?

Well, in your example, you indeed have no choice other than to set up metatables so that things like dialog.getChild("foo") will work. My example requires no metatables at all other than for "plugin", and even that can be easily removed.

This is a good point. I could just assign the methods I want into the proxy table, which is what I was thinking of initially, but... Hmm. I don't think it's a pressing issue - it would be more the thing the plugin author has to design around - but it's something to keep in mind for sure.

David Haley said:
Quote:
This is nothing like that kind of "please everyone" thing.

I don't understand what you meant then when you said that every user's personal preference should be handled by the interface.

I meant to have emphasis on "that kind". It is similar, but I am no more lenient than the language itself. My point is that I am also not so strict that the interface becomes some kind of neutered API. (not to imply yours is, I'm just saying.)

David Haley said:
Quote:
But my lenience also makes it easier for other languages to interface with PPI with their own constructs, whatever they might be.

What is your basis for making this claim? Have you looked at what cross-language communication entails, and how your solution makes things much easier than other proposals?

Yes, I believe I have, and I've given several examples of how other languages (in particular VBScript) can easily implement a PPI interface. I've also explained that using serialize.lua makes it more difficult for another language to parse and translate. My protocol specifically uses Array*() methods to create the translated data, so the only thing the implementation really has to do is figure out what type the data should be sent as.

'Soludra' on Achaea

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

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #115 on Fri 22 Jan 2010 09:02 PM (UTC)
Message
Quote:
I go by feel and intuition, with a good helping of imaginative forethought/critical thinking. That probably says more than anything else about our disagreements.

I'm going to assume that you didn't mean I don't use forethought or critical thinking. :-P

I don't mean any offense at all here, but I think it's hard to argue for core modules on a particular person's feelings; objective metrics are at least "fair". (And if you don't like the objective metrics I proposed, you are definitely encouraged to post others.) Surely you agree that in order to have any useful discussion, we must be basing our judgments on criteria we share. There's really no point in discussing the CS equivalent of vanilla vs. chocolate.

Quote:
My point is that I am also not so strict that the interface becomes some kind of neutered API. (not to imply yours is, I'm just saying.)

The best APIs are the ones that impose a very clear policy which restricts usage just enough that it's obvious how to do the very common things, and still possible to do rather unusual things. Flexibility always comes at a cost (if anything, it becomes more confusing to do things if you have to figure out which of several options is appropriate).

Quote:
Yes, I believe I have, and I've given several examples of how other languages (in particular VBScript) can easily implement a PPI interface.

But VBScript has no notion of function values, so all the arguments made about things being "natural" in Lua do not apply at all. The PPI interface would look dramatically different. This is a loss of consistency. It's not just that function calls look different in different languages; it's that the entire approach to a problem changes even though the claim is made that it's all nominally language-neutral.

Mind you, I'm quite sympathetic to the idea that a crappy language like VBScript shouldn't drive down nicer languages. But, we're talking about cross-language development here, so we're a little restricted in how much we can ignore that crappiness.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Nick Gammon   Australia  (23,042 posts)  [Biography] bio   Forum Administrator
Date Reply #116 on Fri 22 Jan 2010 09:04 PM (UTC)

Amended on Fri 22 Jan 2010 09:05 PM (UTC) by Nick Gammon

Message
Twisol said:


I don't agree with that, to be honest. As I mentioned, passing by name requires that the function be available through _G, which locks out locals, closures, and table entries.


Well the function in _G can be a closure. I used that idea a bit in the module that lets you move windows around. Just to explain what I mean, this is the general idea:


function make_closure (f, colour)

return function ()
         f (colour)
       end -- inner function

end -- make_closure 


OnStartBattle = make_closure (DrawBorder, "red")
OnStopBattle = make_closure (DrawBorder, "black")

ppi.Expose ("OnStartBattle")
ppi.Expose ("OnStopBattle")


The function make_closure actually creates two closures (functions closed with respect to their local variables), even though they are the same core function.

This effectively gives us a OnStartBattle and OnStopBattle functions which "remember" which colour to draw the border in. (I admit these are both in the global namespace in this case).

In any case, assuming you want to be able to call unnamed functions, the PPI module I proposed as an alternative supports that, as in:


-- Or, for anonymous functions:
ppi.Expose ("DoSomethingElse", function () print "hello" end)




- Nick Gammon

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

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #117 on Fri 22 Jan 2010 09:14 PM (UTC)

Amended on Fri 22 Jan 2010 09:19 PM (UTC) by Twisol

Message
True, but you might want to keep a list of closures (i.e. in a table), and you can't really give each one an entry in _G too. I mean, you could, but it really litters the global space, which is why you (okay, me and fadedparadox at least) generally use tables/arrays for namespacing.


True, your version does allow it because it works by key rather than by function name - you're actually storing the value, just like mine. Mine makes everything pass through PPI_REQUEST (yes, I need to change that to INVOKE) instead, so that you only have three global PPI functions (access, invoke, cleanup) rather than as many as the number of functions you exposed. However, I still think your version is somewhat less polished, no offense intended. Mine works in pretty much the same way (barring serialize.lua, and I explained why I went my own way there), except I've generalized the concept (using the same building blocks) to allow access to non-function values through the PPI. I also don't think serialize.lua will handle function parameters properly, so you can't pass function callbacks as you can in mine. Please correct me if I'm wrong!

'Soludra' on Achaea

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

Posted by Nick Gammon   Australia  (23,042 posts)  [Biography] bio   Forum Administrator
Date Reply #118 on Fri 22 Jan 2010 09:27 PM (UTC)

Amended on Fri 22 Jan 2010 09:28 PM (UTC) by Nick Gammon

Message
serialize.lua doesn't handle functions at all.

[EDIT] I doubt you would want to.

- Nick Gammon

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

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #119 on Fri 22 Jan 2010 09:36 PM (UTC)

Amended on Fri 22 Jan 2010 09:37 PM (UTC) by Twisol

Message
Nick Gammon said:

serialize.lua doesn't handle functions at all.

[EDIT] I doubt you would want to.


That's kind of what I meant. You have to pass a reference to the function, just like the PPI-exposed functions do, so to speak - each of yours is stored and kept with an associated identifier. If you want to pass a function between plugins, and have it do what you expect, you have to pass an identifier or use one it already has (the 'name' doesn't cut it because it leaves out so many kinds of functions just based on where they're defined), and pass that identifier in lieu of the function itself. That's what my PPI does, to allow function passing/returning, which in turn makes it much easier to work with the asynchronous callback/notification technique.

For what it's worth, you might want to pass a closure to be used as an async-callback function, too. You'd have a specific item out of many that you want to affect when the event occurs, and make it the upvalue to your closure.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[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.


304,235 views.

This is page 8, 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]