Plug-in Architecture

Started by
12 comments, last by Drew_Benton 18 years, 8 months ago
Hello everyone, lately I've been thinking about the general design of an engine that have a plug-in architecture, and I can't come to a decision. Let's say, for example, that if it is a PURE plug-in engine, then it's parts won't know about each other, right? So, how'd they be going accessing each other parts/variables? One way is to create a messaging system similar to Windows one where with LOTS of messages that are going to work in a similar way. But I don't really think that's a decent idea. So, another way would be to create core parts of the engine pluggable (like renderers, input devices, etc) and go from that. I need your reccomendation on that, which way to go and maybe some info on how to do that. Thank you.
Advertisement
I'd go with your second suggestion. You don't want plugins to be dependent on other plugins beyond the actual definition of plugins for your engine, so if you need plugins to specialize you should split them up. Then when your plugin needs to talk with the renderer the renderer plugin is defined and you can communicate with it without having to worry about what renderer it actually is.

If two plugins are ever dependent on each other, then they are no longer two seperate plugins since it completely violates the concept of code that can be swapped out. Instead, it is a mess and should be given a quick death.
Turring Machines are better than C++ any day ^_~
Do you have any ideas about how you are actualy going to code this?

ace
It's pretty easy to write in both ways, ace_lovegrove. I will use UnitedBytes cross-platform plugin library, extend it so that I can use it within a class, and thats it.
The general idea is to stick with exposable properties and use messaging system to communicate between plug-ins.
Second idea is to create plug-ins as DLLs (which they are) and use interfaces to provide general functionality for each impl .
So, what you are saying, interest86, is that the second way is a better one. OK. I'll try it. Thanks (rates up both).
Have a look at how COM (Component Object Model) works.
Basicly what it does is to:
  • Create pure interfaces (in C++ that would be a class with pure virtual functions and no data)
  • All interfaces inherit the "super-interface" (IUnknown in COM) that only supplies some very basic services, the important one is "QueryInterface" that lets clients query an unkown interface for a more specific one (in COM it also serves to give the object reference counting).

    For a light variant of this you could probably simply build it upon typeid or dynamic_casts.

    Messages are great for when your plugin doesn't need to know (or doesn't care) who it is communicating with but you can easily end up with an overly flexible system that can be very hard to follow (been there done that).

    In a past project I implemented a fairly robust messaging system the problem was that when deadlines were looming neither I or anyone else on the team actually fixed design issues we started to simply send a message and hope that someone-else somewhere sometime would see the message and do something sensible. It pretty much implodeded with a gigantic monolithic message handling function that was to say the least hard to comprehend (and it still didn't actually do everything we had clumps of message handling code everywhere).

    Anyhow, messages are great, just keep disciplined if you plan on using them.
  • HardDrop - hard link shell extension."Tread softly because you tread on my dreams" - Yeats
    What I want to have is an ability for the user/programmer to add an extension to the existing functionality without much effort. I already have interface classes just like in COM and all I want to do is to add that extra freedom at runtime as well as precompile-time. So, the user can either go one way or the other.
    I understand the complicated nature of the question, it's really hard for me to explain what I'm after. All I want is to have my engine is to be extendable, and be extendable easily and without much effort. I can achieve that by using interface classes and restricting user to certain functions, but I'm just not sure that this is a proper way. I mean, look at the HL SDK. You can write MODs (and many people did) with it. That's what I'm aiming at. High flexibility + ease of use.

    Thank you.
    I was playing around with a system where the user can add 'processes' to the engine at compile time. A process being a class that the user wants run per frame. The system was that i had a base class and any class the user wanted to run was derrived from this. Then the class was added to the kernels map of process to run through and they were stored by name.

    This way any process (class) that want to use another class had to know just two things. The header file for the class and its tag in the map. Any class can ask for a pointer to any other class in the kernel and asks the kernel.

    You understand what i mean?

    ace
    I think you definitely need some sort of fixed interface (like in COM) if you are going to make modules switchable at runtime. Unless you have some kind of scriptable interface that the user can modify when they switch modules, the new module will have to have the same properties as the old module. If the modules are DLLs they will have to have the same exported functions, if they are COMponents they will have to have the same interfaces exposed etc.

    Your first idea is not far from Windows messaging or some kind of transaction service. Depending on what your software is doing, it might be quite possible to create discreet packets of information which can be passed around and intercepted by the modules which are interested in them. You could use something like the Observer design pattern for a registration mechanism. You could also use the Iterator design pattern for a mechanism to iterate through the various modules, interrogating each one to see if it supports something that other modules are interested in. (Hope that makes sense)
    "Absorb what is useful, reject what is useless, and add what is specifically your own." - Lee Jun Fan
    As other people have mentioned both methods work.

    My day job is working on a message passing system for control and simulation of autonomous vehicles. It's built from the ground up with support for distributed plugins and real time programming. Most of the time the system is fantastic - debugging can be a pain though.

    Our implementation of plugins provide a very minimal interface (just a run method) and inherit support for basic operations (bootstrap parameters, reads and writes to a real time database) from a base class. Inter plugin message names are supplied configuration files (although we're in the process of upgrading the process to conducted through a scripting interface).

    Advantages
    * The minimal module interface makes inter-language operation a breeze. We have three languages which are currently supported (C++, C and ada) and plans to add support for more (python, C# and lua)
    * Multi-developer support. Most programmers just have to know how to write a simple single threaded application that follows a minimal set of rules (on data passing and termination) and don't have to get their hands dirty with the ins and outs of plugins and communications.
    * Third party software support. Having a minimal interface means that third party implementation details can't leak out of a module (the design tends not to deteriorate as much because you can't 'just add this one little special case').

    Problems in the design include:
    * Loss of type safety in passed messages.
    * Data needs to be serialized before message transmission. This operation is either dangerous (casting and relying upon similar sizes and data layouts on both ends) or expensive (conversion to stings)
    * Data runs through a central 'hub' which is then broadcast to subscribers, an operation which in our case is relatively expensive because of synchronization, and is in most cases overkill because most data is only likely to have one subscriber.
    * As has been mentioned before, if the names of the messages don't match or get out of sync you don't get any results in the best case and block waiting for data that will never arrive in the worst case.
    * All of our plugins run asynchronously - depending on your needs you may need to offer some support for this. I assume this would be an issue in a graphics heavy environment, you certainly don't want a single plugin 'hogging' when you want to be rendering.
    * You end up with a plugin for everything.

    For our work, we develop a large number of very similar systems and it's really convenient for everything to talk seamlessly together. However, it's worth noting that we only really move around data without much structure (lots and lots of numbers), and doing anything else would be a pain to code and likely to be quite slow.

    Personally I think a group of programmers who communicate together would be far more productive with option 2. Option 2 is really just a synonym for design by contract, which makes testing and collabrative development far simpler.
    For my plugin stuff I normally use a server internal registry. It stores a name, a version number and a pointer to a factory that will instanciate the object or pass the pointer to an existing object if the object is shareable). Using that you can create a kinda QueryInterface like method that takes the name, the version and an empty pointer to an interface and it returns the object if successful. Plugins simply register new factories. So it works pretty much like COM.

    I had a message based system with similar intend working. But using it felt a lot less structured. Unless you really design your whole system to be totally message/event based you run into a couple of problems (or maybe that was my designs fault..). I had to introduce a mechanism that allowed to force the server to process all messages in its cue(fifo) until it encountered a STOP message. That way you could synchronously request an interface/value/function, post the stop message and call a special function that processes the fifo until STOP. So that after calling the special function your request had been fulfilled and you could start using the interface.

    Anyways..I'd suggest not using messages unless you have a truely asynchronous system that requires a dedicated event based protocol anyways. For the cases where you need that (networking,..) you could create a plugin.

    Alex

    This topic is closed to new replies.

    Advertisement