Inter Plug-In Communication

  • DISCLAIMER: This post will be long and boring and it will raise non-trivial Java topics (which I am not sure to understand 100% myself, being a C++ programmer, before than a Java programmer...). However, at least the first section Why? might be of general interest.


    Why?


    The two following example scenarios should show why Inter Plug-In Communication (IPIC) might be useful:


    Scenario A:


    1) Assuming we already have an "Area Protection" plug-in with some basic features and a "Money" plug-in also with some basic features;


    2) A new "Shop" plug-in is developed, implementing shops, for instance using chests or crates as item containers.


    3) This "Shop" plug-in would find useful to query the "Area Protection" plug-in for chest accessibility (we do not want buyers to access any chest, only those in unprotected areas or in specially defined shop areas) and to query the "Money" plug-in to check if the buyer has enough money or to tell it to subtract the relevant amount in case of a buy.


    This is to avoid to have (almost) anything in (almost) any plug-in. Once the first two plug-ins provide some basic features, other plug-in can add more features, piggybacking on them. Note that even including the "Shop" feature in the "Money" plug-in would not avoid the need to query the "Area Protection" plug-in for chest accessibility (or vice-versa if the shop feature is implemented in the "Area Protection" plug-in).


    Scenario B:


    1) Assuming there is an "Objects" plug-in which places static objects (and we have one!!!); it may have interesting features like visibility management depending on player distance, or some simple animations (let's say rotating elements);


    2) A new plug-in is developed which implements wind mill (to produce flour or olive oil or whatever) or water-driven power facilities (and electricity management).


    3) The latter plug-in might find useful to ask the "Objects" plug-in to display its mills or power facilities rather than re-inventing the wheel from scratch; it may also take advantage of any improvement the other plug-in may gain over time.


    In practice, IPIC would allow each plug-in to leverage whatever feature other plug-ins already have and decided to share, minimising development effort and maximising features and feature quality, as each plug-in could focus on its 'core business', making it as polished and as robust as possible, without being distracted by nice-to-have but collateral features, which could become the 'core business' of other plug-ins.


    How?


    This is where things go muddy...


    Before looking elsewhere or waiting for some API expansion, I looked at what the current plug-in API has to offer right now. And I found at least two areas:


    *) Custom events (supported by the Plugin.triggerEvent() method). This might be useful for some kind of IPIC, but it seems to be intended for notifying of some context change whomever might be interested, more than to query a specific plug-in for a specific datum or action. It is also cumbersome to get one or more return values (query) from this methodology. There are work-around, involving synchronisation and custom objects defined by each plug-in to pass data back and forth, but they are cumbersome and not very robust.


    *) Direct plug-in access (supported by the Plugin.getPluginByID/Name() methods). This allows to retrieve an instance of a specific plug-in and, potentially, to invoke any of its public methods: quite near to the mark!


    Unfortunately, both methodologies suffer from a serious drawback: both require the target plug-in to be in the CLASSPATH. Apparently, no plug-in .jar is in the CLASSPATH (which makes sense from some point of view), so no plug-in can access the definition of other plug-in classes (or inner classes); trying results in a run-time java.lang.ClassNotFoundException.


    There are ways to dynamically add plug-in .jar's to the CLASSPATH, which involve ugly tricks based on reflection. I tried on a local dedicated server and it works: I could call a public method of a plug-in from another plug-in.


    In principle, it would be possible to encapsulate these tricks in a specific IPIC plug-in, exposing neater methods for other plug-ins to register themselves as sources and publish methods, making the life of plug-in developers easier. Still, there seems to be no way to ensure this plug-in would be loaded first to be immediately available to other plug-ins (registration of a source plug-in is likely to happen at plug-in onEnable() )


    Then, there are the already invented wheels:


    *) JMX and its various flavours of MBean's: it has the advantage to be already included in any JVM and to be relatively easy to use, once you know its concepts. I have not tried it yet in the RW context, but I assume it is going to work. Still, forcing plug-in developers to learn another (slightly esoteric) technology in order to create plug-ins is not going to help a wide adoption of the IPIC concept (rather useful, as we saw).


    Again, it is possible to hide the technicalities in an IPIC specific plug-in, providing other plug-ins with 'simple' ways to publish access to data and methods (if they choose to), but still it would be necessary to ensure that this plug-in would be loaded before any other, and this do not seem possible at the moment.


    *) OSGI is the obvious 'big guy' of the block. However, it might be over-kill and it is likely to require a built-in support form the RW framework itself. So, not for now, I'm afraid.


    *) Java Module System also looks appropriate, but it was not here yet, last time I checked.


    In summary, there are ways to implement IPIC right now. However, all of them have a more or less steep learning curve, require more or less esoteric code snippets to be added to the plug-in source and none is really 'plug and play'. Worth pursuing anyway?


    If anyone has other suggestions, proposals, comments, I am eager to hear them!

  • Unfortunately, both methodologies suffer from a serious drawback: both require the target plug-in to be in the CLASSPATH. Apparently, no plug-in .jar is in the CLASSPATH (which makes sense from some point of view), so no plug-in can access the definition of other plug-in classes (or inner classes); trying results in a run-time java.lang.ClassNotFoundException.

    Hmm... this is indeed a problem (it makes the getPluginByID/Name() methods rather useless). Currently each plugin has an own classloader (in order to be able to unload a plugin properly), unfortunately these classloaders don't know anything about each other. Basically even a minor change (load all plugin by a single classloader) would do the trick, but first we have to make sure this doesn't result in any other issues (especially regarding the ability to reload plugins on the fly).

  • that's why I'm Cramming everything into one plugin, no sense of making 20 plugins when all you have to do is keep adding featues to the one you currently have, I was going to make a separate mail plugin but that will put into EffNet, along with money and shopping.,

    Everyone is entitled of his own preferences, of course. My preference is for distributed development: quicker and of better quality, usually.

    Hmm... this is indeed a problem (it makes the getPluginByID/Name() methods rather useless). Currently each plugin has an own classloader (in order to be able to unload a plugin properly), unfortunately these classloaders don't know anything about each other. Basically even a minor change (load all plugin by a single classloader) would do the trick, but first we have to make sure this doesn't result in any other issues (especially regarding the ability to reload plugins on the fly).

    I expected there were reasons for such a set up and they make sense.


    If it is of any help, this is what I tried: from one plug-in, I accessed the System Class Loaded (ClassLoader.getSystemClassLoader()) to use its addUrl() method to add the plug-in .jar path. This is enough for any other plug-in to access the class def of that specific plug-in. So perhaps it is not necessary to modify the class loader of each plug-in, the system class loader is enough (not that I do understand everything of this, particularly knowing nothing of the internal set-up, but I do my best to pretend I do... :S ).

  • If it is of any help, this is what I tried: from one plug-in, I accessed the System Class Loaded

    Yeah, this would work of course (after making the addUrl() method accessible via reflection [it's ugly indeed, but not that ugly in this case :D ]), but it's favorable to have (a) separate classloader(s) for the plugins when it comes to reloading: Unloading a class during runtime isn't intended in Java, so the only "acceptable" way to reduce the risk of running into any memory issues or resource conflicts would be to unload the classloader (so invoking the garbage collector is supposed to remove all related objects), which wouldn't work with the system classloader :saint:

  • Yeah, this would work of course (after making the addUrl() method accessible via reflection

    Indeed (I summarised a bit the process, but of course you got the details...).

    it's favorable to have (a) separate classloader(s) for the plugins when it comes to reloading[...]

    Yes, I understand (well, let's say I do... ;) ). I am confident you'll find the most "acceptable" way to address this point.

  • i apologize if this seems dumb as i'm pretty new to all of this lol, but perhaps for now we could simply to try work with player attributes for a few things?


    example: a players "currency" value being something along the line of:


    player.setAttribute("modthathandlescurrency_currency", whatever_value)


    or say a player enters a zone, have the area protection mod run something along:


    player.setAttribute("modthathandlesareas_current_area", "name of current area")



    i'm obviously over-simplifying this, and would love to see better inter-plugin communication, but just temporarily maybe come up with some sort of attribute naming convention lol, and provide a list of attributes that your plugin creates/uses.

  • java already allows for a consumer/producer relationship between threads. And it would make a lot of sense to set this up if you are making threads out of each plugin component for asynchronous tasks.

    @zfoxfire: well, this is quite a different thing; communication between threads is one thing; inter-communication between different objects neither of which is responsible for launching the other (and then it cannot held a convenient pointer to the other, as your example shows) is different. Of course there are technologies for this, which I listed in my OP.


    Ultimately however, one of sides will need to access at run time the definition of the other class and this was not possible before the last update (I have not tested this point yet, but I assume now it is possible).


    At this point, one plug-in can directly and synchronously access the public methods of another plug-in via the API Plugin.getPluginByID/Name() methods and cumbersome asynchronous methods like the ones described by @nobotious and zfoxfire are no longer needed.

  • i apologize if this seems dumb as i'm pretty new to all of this lol, but perhaps for now we could simply to try work with player attributes for a few things?

    Yes, that's actually possible. Every plugin accesses the same "player objects", so setting an attribute makes it available for all other plugins as well ^^


    but just temporarily maybe come up with some sort of attribute naming convention lol

    Since attributes are shared, it's indeed important to make sure that another plugin does not overwrite your attribute. The best thing would be to use a proper naming convention (i.e. just something like the package name etc)


    (I have not tested this point yet, but I assume now it is possible)

    It should be possible indeed, but in case it still doesn't work, please let us know :)

  • Since attributes are shared, it's indeed important to make sure that another plugin does not overwrite your attribute. The best thing would be to use a proper naming convention (i.e. just something like the package name etc)

    Well, so far I have prepended each attribute key with the plug-in name, feeling that the full package name would be too long and a waste of memory to be repeated for each key (for instance, my GPS uses 7 attribute keys for each player), but it may make sense, after all.

    It should be possible indeed, but in case it still doesn't work, please let us know :)

    I'm going to test it rather soon (right now I'm debugging a strange NullPointerException with my GPS after the update) and I'll let you know, but I am confident.

  • @red51: I did test the inter-plug-in call and it works flawlessly! From plug-in A, I could call:


    1) a non-static method of plug-in B by retrieving it with Plugin.getPluginByName() and calling the method of the returned object
    AND
    2) a static method of plug-in B with the usual syntax Class.method() (of course, no need of Plugin.getPluginByName() in this case).


    In both cases, the API located the correct target plug-in. The only, obvious, requirement is at develop/compile time: add the .jar of the target plug-in B in your project

  • Sorry, I cannot understand the logic:


    *) the command you quote (p.sentTextMessage(""+"THE ATTRIBUTE"), once capitalisation corrected) is not a test and does not return anything
    *) the effect of this command is just to write the constant string "THE ATTRIBUTE" in the player chat, which tells little about what is happening.


    - How the attribute is written in plug-in A?
    - How the attribute is read back in plug-in B?

  • Plugin A


    Code
    String value1 = (String)player.getAttribute("PlayerTestMoney");
    player.sendTextMessage("Show the Value:"+value1);

    Plugin B

  • player.setAttribute("PlayerTestMoney",76657667);
    [...]
    String value1 = (String)player.getAttribute("PlayerTestMoney");

    This results in a ClassCastException, since you cannot cast an Integer to String. Either you have to store a String (i.e. player.setAttribute("PlayerTestMoney", "76657667");), or convert the Integer value by using the "String.valueOf()" method: String value1 = String.valueOf(player.getAttribute("PlayerTestMoney"));

Participate now!

Don’t have an account yet? Create a new account now and be part of our community!