Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an official plugin mechanism #85

Closed
melonjs opened this issue Aug 3, 2012 · 24 comments
Closed

Implement an official plugin mechanism #85

melonjs opened this issue Aug 3, 2012 · 24 comments
Milestone

Comments

@melonjs
Copy link
Collaborator

melonjs commented Aug 3, 2012

to make it easier and more "future-proof" when integrating 3rd party libraries (physics, audio, gamepad, etc...).

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 9, 2012

@parasyte

So, here is what I have in mind (let's use this as a starting point for discussion)

Objective :

  • provide a user-friendly way to extend the engine while integrating 3rd party libraries

Requirements :

  • define a basic skeleton for plugins (to be defined)
  • provide a useful method to "apply" the plugin, either by adding a specific function to the Object object, or through a me.utils like function (i don't know which one is better). Note that this is already what is done by the following method when applying user-defined properties to object : https://github.com/obiot/melonJS/blob/master/src/level/TMXUtils.js#L59
  • the plugin mechanism should be able to report error in case the targeted function (to override) does not exist, or if the function signature (parameters) does not match
  • the plugin mechanism should be able to apply some version control (plugin version against minimum melonJS version)

Any thought ?

@parasyte
Copy link
Collaborator

Just thinking of some features that I needed when working with Chipmunk:

  1. A method of synchronizing the two engines. Seems like persistent objects can be used for this now.
  2. Execute a callback on pause & resume (in a way that cannot be overridden.)
  3. Execute a callback on me.game.init
  4. Execute a callback on me.game.addEntity

So the first one is done...

The second one is doable with either support for multiple callbacks, or a publish/subscribe pattern.

Pub/sub pattern is also good for the remaining two... even better than supporting multiple callbacks all over the place.

The last one is also probably not "exactly" what I needed, but it's what I went with. The real need was to capture all of the unnamed polygon/polyline objects so I could create the corresponding Chipmunk shapes. I'm not sure of a better way to manage that. Ideally capturing them at creation-time, so that's what I ended up with.

About the pub/sub pattern: there's a nice little lib I used called MinPubSub (MIT License) which establishes a method for any components to communicate with one another indirectly. Take the pause/resume feature as an example: instead of supporting a callback by setting me.state.onPause and me.state.onResume, the pause method just has to publish a message:

publish("me.state.pause");

Where the string given is the "channel" that user code (and plugins) will subscribe to:

subscribe("me.state.pause", function onPause() {
    // ...
});

This will allow as many subscribers as we like, and they can all act on the "me.state.pause" message; Maybe a user-subscriber will show a "pause screen", Chipmunk and other engines that need to remain synchronized can reset their internal timers properly, etc.

It makes more sense in the case of me.game.addEntity (the array is a list of arguments passed to the listener):

publish("me.game.addEntity", [ entityType, zOrder ]);

And listeners:

subscribe("me.game.addEntity", function onAddEntity(entityType, zOrder) {
    // ...
});

With the pub/sub pattern, there's no need to "install" hooks, or do anything with objects or special methods... just create a series of subscribers that interact based on messages from the engine. And the engine can also subscribe to any messages it might be interested in -- allowing plugins and user code to communicate back to the engine.

My final thought is that versioning is very easy: melonJS provides an accessible property to advertise its version number (bonus points for providing a version comparison method!) probably in the me.sys namespace. Version number comparison is non-trivial, eg. http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number so a method would be most helpful.

A plugin might ensure the melonJS version is ok with something like this:

if (!me.sys.versionGE("0.9.5")) {
    alert("melonJS is too old! Expected 0.9.5, got " + me.sys.version);
}

Methods like me.sys.versionGE (greater-or-equal) and me.sys.versionLT (less-than) will compare the internal melonJS version (me.sys.version) to the provided version string.

@parasyte
Copy link
Collaborator

Oh, and I forgot to mention namespacing the publish/subscribe functions; MinPubSub supports it easily enough, and you can just use me.util as a namespace instead of window, if you like. That would give you me.util.publish and me.util.subscribe

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 11, 2012

I like the idea, and it's both very small (in terms of size) and powerful !

Do you think however I could somehow integrate into melonJS (of course keeping the original author and license) as I like to lower as much as possible the amount of dependencies with 3rd party libraries.

@parasyte
Copy link
Collaborator

I believe it's fine to integrate into melonJS, because it's released under the MIT license. I recommend using the source file (https://github.com/daniellmb/MinPubSub/blob/master/minpubsub.src.js) and copying the full license text from README.md into the comment at the top. That should be enough.

Also, doing more thinking about the version comparison stuff, I like a more "normal" single compare function better: you pass two args (or optionally just one; the second parameter will be implied as melonJS's version number!) and it returns a number:

n < 0   if arg1 < arg2
n == 0  if arg1 == arg2
n > 0   if arg1 > arg2

@ghost
Copy link

ghost commented Aug 12, 2012

I'm thinking I could do the chipmunk shape creation in a much cleaner way if there was a 'me.game.levelLoaded' message published. by then I have access to all of the level objects so I don't need to do any other hooking.

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 13, 2012

Hi Jason,

Sorry for the low activity recently, but I really had to take a few days off this week :)
Else I have everything ready for the 0.9.4 release (only remaining task is for me to lose again one hour trying to understand how to tag this release in github, and upload the new pages of the website). I’ll do that by tonight (since I have not received any further negative feedback on the current one).

This to say that I’m ready to move to the 0.9.5, I’ve been thinking about the pubSub library and that’s really something I like. Just need to figure how to cleanly integrate it in melonJS (I think before you suggested to add the two function directly under the me namespace which I tend to agree with as well : me.subscribe, me.publish), and how to properly document the function uses can subscribe to.

And to come back on your initial question, since this will allow us to have “multiple” callback kind of solution, yes you could definitely then use it when the level is loaded. Other place I wanted to put one as well is on the timer update function (me.timer.update), for this last one though, I also need to rework the class a little first.

Does what I say making sense today ? (I need more sleep this morning)

Olivier.

@parasyte
Copy link
Collaborator

Yeah, that sounds great.

So the minpubsub.src.js is just a self-executing anonymous function, you can easily run it within the namespace you want it to inherit from. But probably an easier integration is replacing the this parameter passed to the function with the explicit namespace you want; me, or me.utils, ... It will add three functions to that namespace: publish, subscribe, unsubscribe.

I think you're aware, but you can also add any documentation to the source file for melonJS. My suggestion for a documentation pattern is including a short description in subscribe to declare that it's for subscribing to channels -- and for every function that uses publish, update those documentation comments to include which channels are published along with the args that are passed.

There may be some internal/private APIs that publish as well, and no documentation will be generated. In those cases, I think it's best to document the publish in some very closely related public API, or within subscribe if it does not fit elsewhere.

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 13, 2012

version 0.9.4 tagged (took me one hour as I said!) :)
I'll publish everything tonight (it's all at home)

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 14, 2012

MinPubSub integrated :)

for now it's directly under the me namespace, don't know if it will stay there, but it's working quite nicely.
for now, I added a first published message when a level is loaded for testing.

(by the way, just changing the this and replace it with "me" was working nice, but then it screwed up documentation generation, so for now i added it manually inside)

@parasyte
Copy link
Collaborator

ok. I see the cache variable d.c_ is being kept in the window context. Might want to change that too.

Also I just sent you a pull request which adds three more publish notifications: me.state.onPause, me.state.onResume, and me.game.onInit. The last one is totally new; there's no analogous callback in 0.9.4 or earlier. https://github.com/obiot/melonJS/pull/87

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 17, 2012

Hi Jason,

out of curiosity, what do you have in mind for the me.game.onInit ?

Else I was thinking yesterday that I should maybe move the pubSub stuff somewhere else (like me.event or me.message, or something else). The problem is that end-users should be able to easily figure out which channels to be used, and this would allows to define some constants that could be easily then added into the documentation as well.

other possibility is to add all "system" defined channel/topic in the documentation of the subscribe function itself.

@parasyte
Copy link
Collaborator

Hi Olivier,

Specifically, I'm using me.game.onInit to initialize plugins at the proper time. Here's an example from the "ChipMelon" plugin that I just updated to use all of these channels: https://bitbucket.org/parasyte/lpcgame/src/89142988f8dd/lib/cm.js#cl-255

I have to add a persistent object (the "Stepper") and get the canvas height, and me.game.init seems like the right place to do that.

I'm ok with having the pub/sub methods moved. And constants do seem like a much better idea than using arbitrary strings everywhere.

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 17, 2012

I see, good :)

For the second point , any suggestion on the naming : me.pubSub looks good no ?

@parasyte
Copy link
Collaborator

It looks kind of funny; me.pubSub.publish and me.pubSub.subscribe. :)

Any of these?

  • me.event
  • me.action
  • me.notify
  • me.channel
  • me.message

Ordered by least to most typing. ;)

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 17, 2012

Yeah, you right, but it was to keep the reference to the original name.
But then I guess me.event is a better choice, since anyway it's an event system :)

thanks !

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 17, 2012

Done. I'm afraid however you will have to change your code again :(
(should be the last time for this at least!)

@parasyte
Copy link
Collaborator

That's fine! It is the price one pays for using bleeding edge software. ;)

It looks great! Thanks!

@melonjs
Copy link
Collaborator Author

melonjs commented Aug 17, 2012

Bleeding edge, yeah ! :)
I must say I’m very excited about melonJS recently, and it’s just awesome to have someone else bringing fresh and new ideas for it !

@parasyte
Copy link
Collaborator

Hi Olivier,

I've implemented a handful of things for this ticket in my fork:

There are some other small fixes, too. I'll issue a pull request shortly. :]

melonjs pushed a commit that referenced this issue Aug 18, 2012
…1e380895

Implement more pieces of issue #85, and various changes.
@melonjs
Copy link
Collaborator Author

melonjs commented Sep 12, 2012

So, back on the plugin mechanism :

Two topics :

  • Register a plugin
    I was thinking about defining a new namespace (me.plugin) that would provide :
    1. A “Base” Plugin class that would need to be extended (though I’m not yet sure of what to put inside, except some stuff for version checking)
    2. one “register” method to “register” the plugin within the melonJS namespace and check for various stuff (min version, name conflicts, etc...)
  • Extend / Replace a melonJS function
    While searching the net for something completely different, I actually stomp on the following, that describes the plugin API for the YUI Library :
    http://yuilibrary.com/yui/docs/plugin/

I’m not saying that we should reuse their code, but this plug method is what I actually had in mind, and the same could be easily added to the object definition in melonJS to easily replace or extend existing function (I guess we could use as well the “plug” name ?).

Another idea would be to add an additional parameter to the function that would precise how to extend it :

  • “replace” : to completely replace the target function if existing (else just add it)
  • “append” : add the provided code at the end of the target function
  • “prefix” : (or “prepend”?) add the provided code at the beginning of the target function

(though the two last ones, are maybe kind of similar with the existing callback mechanism)

@parasyte
Copy link
Collaborator

I agree that having a cleaner way to extend methods than monkeypatching is a good idea. The YUI plugin method looks a lot like the event listener pattern, which is interesting.

melonjs pushed a commit that referenced this issue Oct 1, 2012
Ticket #85 - Implement an official plugin mechanism
@melonjs
Copy link
Collaborator Author

melonjs commented Oct 1, 2012

So, I just merged into the main branch the base stuff for plugins.

Basically it provides two things :

  • a me.plugin.Base class for plugins, still quite simple for now and just check for the minimum melonJS version (we will see with real example what could be added). Once registered, plugins become also available under the me.plugin namespace (this could be useful once having different plugin working together)
  • a utility function to patch any melonJS function, with the ability to call the parent function

see under me.plugin for more details and basic examples :)

@parasyte , I guess it's time to "port" your chipmunk plugin now ... :)

@melonjs
Copy link
Collaborator Author

melonjs commented Jan 21, 2013

I'm closing this one, as I consider it now as implemented and did not received any further feedback.

@melonjs melonjs closed this as completed Jan 21, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant