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.
Entire forum
MUSHclient
Tips and tricks
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 page
Pages: 1
2
3
4
5
6
7 8
9
10
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #90 on Thu 21 Jan 2010 08:35 PM (UTC) |
Message
|
Quote: The table thing is not a core issue, of course, but I'm pushing it so much because the alternative simply boggles me (duplicating tables)
You can think of the "alternative" as either duplicating tables, or simply not supporting tables with repeated subtables. One way of looking at it makes it a "bug", the other makes it a known not-supported feature. (This is quite serious actually and not meant to be facetious.) I think you have drawn a line too strictly between what is "technically correct" and what is "good enough to be supported". (I also think the line is somewhat arbitrary because other things are "technically correct" or possible etc. but not supported, but that's another story.)
Quote: And I need to manage PPI identifiers for exposed methods so that you can pass and return functions, which is key to the asynchronous callbacks technique.
No you don't, not in general -- you only need to do all this because of other choices you have made. If a function is nothing more than its actual name, you don't need to worry about creating, tracking and communicating all these "PPI identifiers". |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #91 on Thu 21 Jan 2010 08:40 PM (UTC) Amended on Thu 21 Jan 2010 08:46 PM (UTC) by Twisol
|
Message
|
David Haley said:
Quote: The table thing is not a core issue, of course, but I'm pushing it so much because the alternative simply boggles me (duplicating tables)
You can think of the "alternative" as either duplicating tables, or simply not supporting tables with repeated subtables. One way of looking at it makes it a "bug", the other makes it a known not-supported feature. (This is quite serious actually and not meant to be facetious.) I think you have drawn a line too strictly between what is "technically correct" and what is "good enough to be supported". (I also think the line is somewhat arbitrary because other things are "technically correct" or possible etc. but not supported, but that's another story.)
There's no point to removing the functionality, though, because the components that make it up are also used in other parts of the library. Again, I use the cache to ensure that each table gets a separate variable (for reasons I've explained previously), so this comes for free. I could modify it so it doesn't do this, but there's no real gain to it.
David Haley said:
Quote: And I need to manage PPI identifiers for exposed methods so that you can pass and return functions, which is key to the asynchronous callbacks technique.
No you don't, not in general -- you only need to do all this because of other choices you have made. If a function is nothing more than its actual name, you don't need to worry about creating, tracking and communicating all these "PPI identifiers".
Yes, I do. I want to support methods that might not have a name - that's exactly what allows you to do this:
PPI.Expose("foo",
function(bar)
-- baz
end
)
This is intuitive, especially if you'll never use that function outside of client invocations. Plus, I need the identifiers so that I can properly support functions as parameters and returns - yes, ones without names, such as an unnamed closure! That's precisely how iterator factories like ipairs() and pairs() work, no? Certainly you might not return an iterator, but the concept has very valid use.
EDIT for clarity on the example.
EDIT: Also, I'm not sure how many other languages your name-based approach would support. By working with PPI-specific identifiers, I can decouple the idea of a 'method' from the language's implementation. |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #92 on Thu 21 Jan 2010 08:57 PM (UTC) |
Message
|
Quote: Yes, I do. I want to support methods that might not have a name
Why? Is the gain of supporting nameless functions really worth the cost? Who cares if that's how things like 'ipairs' work; the question is: do we need to do that?
I mean really, do we actually need to support sending around closures? I find that remarkably unlikely, and it imposes pain upon everybody. More work has been created to support something.
I think it might help if you thought about this as a "market study" rather than "can we solve technical problems". You're adding features which require more technical solutions. But I do not believe you have really asked the question: how useful are these features actually going to be in practice? (As a general hint, saying "oh, we might want it one day" is not a satisfactory answer when it creates additional complexity.)
Frankly, the example you gave of passing an unnamed callback is the kind of thing that is "cute" to do, but not cute enough to warrant extra complexity.
Really, when compared to this:
PPI.Expose("foo",
function(bar)
-- baz
end
)
why is this so much more terrible:
function foo(bar)
do_something_with_bar()
end
PPI.Expose("foo")
OK fine, you've added a single name to the global namespace; oh no. Is the point of all this cost just to avoid adding that name to the global namespace?
Perhaps the most succinct way of making my point is: yes, you think you want that, but you shouldn't want it as an API designer unless you can justify its existence with very clear use cases where a practical problem is being solved.
Note that I'm not saying that it is impossible that we want to pass around unnamed function values. What I am saying is that we have by no means established that we do want that.
Quote: Also, I'm not sure how many other languages your name-based approach would support. By working with PPI-specific identifiers, I can decouple the idea of a 'method' from the language's implementation.
I'm not sure what you mean. All languages can identify functions by their name as they call them, although you might need an eval statement to resolve the name reference. (But if you don't have this, the simple approach falls flat on its face anyhow, let alone the more complex approach.) Not all languages can pass around function values.
I'm not sure what you're gaining by decoupling these two ideas other than supporting unnamed functions, a concept that several languages don't even have. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #93 on Thu 21 Jan 2010 09:08 PM (UTC) |
Message
| We're back to one of my initial arguments (with a slight twist):
test = PPI.Load("my id")
a = function() print('1') end
PPI.Expose("a")
-- or alternatively, since you mentioned using debug.getinfo() to get the name
PPI.Expose(a)
b = test.a
a = function() print('2') end
b()
With your approach, 2 is printed out. This doesn't makes sense to me, because if we retrieve the value of 'a', that value should be our own copy of a reference to that function, just as it is with any other PPI value including tables, and just as it is in Lua itself. With my approach, the above prints out 1.
It's certainly not just 'cute'; in the case of returning an unnamed closure, it's very, very useful, especially in the context of a resource plugin. |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #94 on Thu 21 Jan 2010 09:16 PM (UTC) |
Message
|
Quote: With your approach, 2 is printed out. This doesn't makes sense to me, because if we retrieve the value of 'a', that value should be our own copy of a reference to that function,
And it seems perfectly reasonable to me, if not exactly what is desirable! If the function is changed, the version that people get when they call it changes, and I would certainly not want to encourage the very wonky behavior of exposing some function and then pulling the rug out from under people. Your example seems insane to me. :-) Why would you possibly want to do such a thing? Your objection seems somewhat contrived; what is the real-life scenario where this behavior causes a problem? (And that's assuming that what you think is reasonable is in fact the objectively reasonable thing on an absolute level; as seen here, I don't really think it is.)
Besides, you keep talking about Lua semantics. Lua semantics, in your example, is that the global function 'a' is replaced with the 2 version, and any future invocation of the name "a" should give you 2. Why should exposing a name over an API be any different? Isn't it reasonable that if I change my mind about what some global symbol means, everybody who uses that global symbol should get that new version?
The point I am trying to make is not that you are wrong and I am right. The point is that it's not necessarily so obvious as you seem to think, and that there isn't one Absolutely Correct Answer.
Quote: in the case of returning an unnamed closure, it's very, very useful, especially in the context of a resource plugin.
Give me an example. Hypothetical situations are uninteresting because obviously something will be impossible given enough constraints. The interesting question is: which constraints are actually realistic? |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #95 on Thu 21 Jan 2010 09:29 PM (UTC) Amended on Thu 21 Jan 2010 09:36 PM (UTC) by Twisol
|
Message
|
David Haley said: Besides, you keep talking about Lua semantics. Lua semantics, in your example, is that the global function 'a' is replaced with the 2 version, and any future invocation of the name "a" should give you 2. Why should exposing a name over an API be any different? Isn't it reasonable that if I change my mind about what some global symbol means, everybody who uses that global symbol should get that new version?
No, that code above is the equivalent of this native code:
a = function() print('1') end
b = a
a = function() print('2') end
b()
This returns 1.
I can see your point, but I personally think we're into the realm of personal preference now. I prefer this approach because it is closer to Lua semantics.
Besides, you could always just use myppi.MethodAgain(params), which would always return 2 in the previous example.
David Haley said: The point I am trying to make is not that you are wrong and I am right. The point is that it's not necessarily so obvious as you seem to think, and that there isn't one Absolutely Correct Answer.
That's why I think it's more about personal preference at this point; as I've shown, my version can cater to both preferences.
David Haley said:
Quote: in the case of returning an unnamed closure, it's very, very useful, especially in the context of a resource plugin.
Give me an example. Hypothetical situations are uninteresting because obviously something will be impossible given enough constraints. The interesting question is: which constraints are actually realistic?
Alright, iterators are an admittedly good example. A plugin might store a history of messages for each of its clients, and the client could iterate over them easily by using such a closure. This is useful for contextual evaluation, for example; you might search your history and do something specific if a certain event occurred before.
EDIT: As a slightly better example, the resource plugin would store a complete history of messages, and a plugin could iterate over it even if it didn't get any of them (i.e. if it was added after the fact). |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #96 on Thu 21 Jan 2010 09:44 PM (UTC) Amended on Thu 21 Jan 2010 09:45 PM (UTC) by David Haley
|
Message
|
Quote: No, that code above is the equivalent of this native code:
Sorry, I don't agree. It's the equivalent only because you made it be that way by your own construction. If you are registering function names and not function values, your argument doesn't hold. You are mixing Lua semantics with the PPI semantics that you have constructed.
Regardless, you still haven't explained why one would actually want to do such a thing, though. I do not think a protocol should support stuff just because it's possible, but because it is desirable.
It sounds like what you really want is a Lua-to-Lua protocol, anyhow, given how much weight you put on preferring Lua semantics. It's unclear to me why Lua dictates everything when the explicit goal is to support cross-language, cross-plugin communication. What you're suggesting is downright weird if not impossible in languages like VBscript; I make a stronger statement in that it's also rather odd in Lua to do such a thing. Why would you ever expose a name, and then change what that name means?
Quote: EDIT: As a slightly better example, the resource plugin would store a complete history of messages, and a plugin could iterate over it even if it didn't get any of them (i.e. if it was added after the fact).
Why must this be implemented as an iterator and not simply getting a list of the messages? The recipient would then use its native iteration as opposed to having to iterate over the PPI API.
If iteration is truly critical, then implement an iteration protocol using functions that have the iteration state as their argument one way or the other.
Don't forget that Lua isn't the only language here. VBScript might not have anything resembling this iterator construct you're discussing. In fact, this might end up creating rather awkward code in VBScript! |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #97 on Thu 21 Jan 2010 10:43 PM (UTC) |
Message
| Another developer and I just discussed PPI in depth after he read this topic, and I discovered a bit of a hole in your method that affects usability.
Functions defined as values within tables have no name. Hence, you can't expose them using your method with debug.getinfo. Table functions are common enough and useful enough that it's not acceptable, in my honest opinion, to lock them out.
t = {a = function() end}
tprint(debug.getinfo(t.a))
Output:
"nups"=0
"what"="Lua"
"func"=function: 03096DD8
"lastlinedefined"=1
"source"="Command line"
"currentline"=-1
"namewhat"=""
"linedefined"=1
"short_src"="[string "Command line"]"
Happily, I also have a concrete and insanely useful example for closures. Lets take my widget framework, and create a plugin that manages some widgets. Lets say you have a second plugin that wants to do something with the first plugin's button is pressed. An idea I had, which is, yes, very practical, is to Expose a method in the resource plugin that returns a table of functions that apply specifically to the requested widget. Through this interface, you could register your own callback for a button event, and do any number of other operations the resource plugin allows, through an intuitive, easy-to-understand interface. You can treat the table returned as though it was the widget itself.
This is important, by the way, because I had been trying to come up with a solution to this long before I recreated PPI. Up until this, there was no easy way to allow other plugins to interact with my visual widgets, but PPI paves the way beautifully. |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #98 on Fri 22 Jan 2010 12:51 AM (UTC) |
Message
| It occurs to me that I could just use tostring(func) as the PPI identifier (i.e. "function: 03096DD8"), but it would be different for every language and I think it's probably easier just to keep it standardized. |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #99 on Fri 22 Jan 2010 05:42 AM (UTC) |
Message
|
Quote: Hence, you can't expose them using your method with debug.getinfo.
I never said that was useful, FWIW. I only proposed it because you insisted on passing function values.
I disagree strongly that table functions are so common, but there's really no point in making sequences of contradictory assertions. The most succinct statement at this point is that I need empirical evidence to convince me, not statements of possibility or personal belief of usefulness.
Quote: Happily, I also have a concrete and insanely useful example for closures. Lets take my widget framework, and create a plugin that manages some widgets. Lets say you have a second plugin that wants to do something with the first plugin's button is pressed. An idea I had, which is, yes, very practical, is to Expose a method in the resource plugin that returns a table of functions that apply specifically to the requested widget. Through this interface, you could register your own callback for a button event, and do any number of other operations the resource plugin allows, through an intuitive, easy-to-understand interface. You can treat the table returned as though it was the widget itself.
I have to admit that I don't really view this as a compelling argument. It smells of separating stuff because you can, not because you should.
Anyhow, you're still quite strongly Lua-centric. I'm not sure why you're trying to argue you're not. It's not like a Lua-only cross-plugin communication layer isn't useful. In fact, if you're guaranteed to only be dealing with Lua, you can remove an awful lot of restrictions. (And, you can use "standard" serialization libraries like Pluto to really serialize stuff.) |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #100 on Fri 22 Jan 2010 06:09 AM (UTC) Amended on Fri 22 Jan 2010 06:13 AM (UTC) by Twisol
|
Message
| It's not separating so much as it is collaborating. The idea behind the widget thing isn't to separate functionality from one plugin into many, but to allow entirely separate plugins to cooperate with the original plugin in question.
I will freely admit that I may be thinking from a somewhat Lua-tainted standpoint, because I am working in Lua after all. However, I still strongly maintain that the PPI protocol layer (the serialized translated data strings) is very language independent. If you like, call the function identifier a message, and the translated data the content. This is looking very similar to OnPluginBroadcast with only a slight change in terminology. The difference is that it has the potential to be friendlier and more intuitive, especially regarding the actual data being sent. And IDs/messages can be sent to separate functions rather than the same catch-all one, and to specific/single plugins rather than all of them.
The actual inner workings of a PPI port don't need to resemble anything close to this version, nor does the interface itself. As a last resort, a port could just import the string data into MUSHclient arrays, and provide an API to translate individual entries from them manually.
I know you didn't ask about this directly, but I want to mention anyways: I wrote my own translation layer for PPI because the serialize.lua module is very Lua-centric. Its output is designed to be loadstring()()'d, and it is more effort to deserialize it properly in another language than it is to translate my PPI protocol. One reason is that I use Array*() methods to do the exporting/importing, which every language has access to. Another reason is that every piece of data is paired with a type-code that shows exactly how that piece of data should be translated.
Incidentally, Pluto happens to be a third-party library which MUSHclient doesn't include by default, which defeats the purpose of including PPI with the standard distribution. At least serialize.lua would be guaranteed to be there.
On the subject of a Lua-only PPI, yes, I could do that. I would still have to tinker with functions so that they're referred to in the source plugin and not actually serialized (which would kill upvalues and such, making them useless for plugin communication) or even ignored outright. I think I probably would have stuck with my protocol anyways, though it would undoubtedly allow for any-type keys too. But that's kind of the only 'restriction' I can see you referring to, though. What others do you see? |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | top |
|
Posted by
| Fadedparadox
USA (91 posts) bio
|
Date
| Reply #101 on Fri 22 Jan 2010 06:21 PM (UTC) |
Message
|
David Haley said:
I disagree strongly that table functions are so common
I don't know if it's just me, but I use table functions quite often. | top |
|
Posted by
| Fadedparadox
USA (91 posts) bio
|
Date
| Reply #102 on Fri 22 Jan 2010 06:32 PM (UTC) |
Message
| I'd be interested in an easier way to talk between my scripts, but since I write entirely in Lua, I wouldn't care if multi-language capability was included or omitted.
What interests me most is the core-modules concept this would make easier. For me though, and the reason I commented, was that I would need functions inside tables. | top |
|
Posted by
| David Haley
USA (3,881 posts) bio
|
Date
| Reply #103 on Fri 22 Jan 2010 06:49 PM (UTC) |
Message
| 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.
function a()
return 123
end
t = { myfunc = a }
send_to_plugin("bobs_plugin", t)
voila, tables with functions inside them being sent "over the wire". I still need a use case, though.
Anyhow, what is VBScript supposed to do with a table that contains "functions"? (The notion doesn't even exist, AFAIK...)
It'd be easier to do this kind of stuff if things were Lua-only. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | top |
|
Posted by
| Twisol
USA (2,257 posts) bio
|
Date
| Reply #104 on Fri 22 Jan 2010 06:56 PM (UTC) Amended on Fri 22 Jan 2010 07:00 PM (UTC) by Twisol
|
Message
| That's a rather roundabout way to go, considering that many people define the function directly in the table, or create the table and then insert the function. 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.
Don't think of them as functions. They are message IDs. VBScript can, as I suggested, import the data into MUSHclient arrays and provide a simple API to access the entries individually. It never has to manage a structure, because it's helpfully contained by the arrays. So you might have a CallPPI() method that takes the index of the message ID to invoke in the array, and the parameters (varargs if it has it, an array if not).
EDIT: I have a better idea with VBscript involving the use of structs for individual tables/entries, and supplying the API for each entry, but since I've never used the language I don't know if it's plausible (although I certainly think it is). |
'Soludra' on Achaea
Blog: http://jonathan.com/
GitHub: http://github.com/Twisol | 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,242 views.
This is page 7, subject is 10 pages long:
1
2
3
4
5
6
7 8
9
10
It is now over 60 days since the last post. This thread is closed.
  Refresh page
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.