Hi Mauro,
There's sort of two issues here; the API, and L3DT. I’ll try to break them apart:
API
I suppose the main paradigm of the API when it comes to interaction between modules is functional programming (though it’s object-oriented when dealing with data.) Indeed, the idea of a ‘module’ in this context is simply a handy container for functions, and modules are effectively transparent to most of the API. The extension of this is that ‘mangers’ are themselves an abstract idea, being just a function or a collection thereof.
So, having just poured water on module-driven design, let’s describe how modules interact:
Each plugin exports functions to the API (L3DT does this too), which may be called by themselves or other plugins, or indeed by L3DT. Other than to define how functions may call other functions, I’m not going to specify interfaces or patterns for the API in general. This is because the patterns and interfaces used in different managers will vary greatly from manager to manager, depending on what is most convenient for what they do, and on the tastes of the developer in question (remembering that managers don’t have to be in L3DT.) Patterns will be emergent properties, not uniform across the landscape.
Here are some examples:
To run a calculation in parallel on a map, I pass to the thread manager (actually, just a function) a handle to the map(s), a handle to the function I want each thread to run, a handle to a list of tiles (defining areas for each thread), and a handle to a list of function arguments. The thread manager then processes the tiles in order, calling as many threads as it sees fit, each of which run the function passed to the thread manager. The manager doesn't know the details of the maps or calculations it will be running, and doesn't care. It doesn’t even know the function arguments (they are provided ‘from above’). It just runs the function given to it with the list of arguments provided on the maps and tiles provided. This way basically abstracts the thread management from the calculations themselves, which is a neat way to do things in parallel (see Google’s MapReplace algorithm.)
On the other hand, the I/O manager has a specific interface. You may call the file I/O functions (with a well-specified set of arguments), those functions check the arguemnts to find the right file I/O function to save/load the map, and call them with the appropriate arguments. There is no flexibility in this interface, because there is no need, and because it would only make things messy.
Two opposing ways to do things, but both appropriate for their task. Neither are particularly module-centric, though.
L3DT
By ‘L3DT’ I really mean the ‘L3DT pipeline’, i.e. the functions and procedures and interfaces that I’ve built atop the API, which are now or will be exposed to the API. Of course, here I have set patterns and interfaces, and on the whole I lean towards the facade DP above others. I steer away from pipes and filters in particular because I find the pipework to be restrictive in terms of parallel processing, or non-linear (forked) processing. All operations are still generally broken down into small parts and done in order (or in parallel), but each operation generally doesn’t know what came before or will come after or is happening at the same time. That’s all handled ‘from above’.
In any case, irrespective of my choices developers have the freedom to make their own managers and interfaces. One could, for instance, make a whole World-Machine-esque interface, calling on whatever functions of L3DT or other plugins that may be useful, and writing the wrapping code to make them behave like pipes and filters. The API lets you do this, but it’s not how ‘the L3DT pipeline’ works.
mauronen wrote:- each Manager is a little subsystem that expose a unified interface. The subsystem can change or be replaced with another that implements same interface
Agreed. Being able to swap managers (or rather, replace functions) is a necessary feature. Not implemented yet, but will be soon, probably using a plugin (doesn’t need to be a core API feature).
mauronen wrote:- a Manager manages a small and independent process (because is a subsystem), but is only a step of a wider process. Each Manager is connected with previous and next Managers by a channel.
I don't necessarily agree here. If I were to have a pipeline pattern such as this, it would only be because I have an over-reaching manager that calls them in order (in fact, I do in L3DT.) Generally, each component has no idea about ‘where it is’; it's the pipeline manager's job to keep track of this.
mauronen wrote:- Managers exchanges messages of same type (a landscape)
I don't do much, if any message passing at the moment. Each plugin/manager/function/whatever simply does what they need to do, and if something needs to be stored/flagged for use later or elsewhere, they set an appropriate value in the project settings tree. You can see many such examples of this in the 'def.xml' files. Each calc/plugin/manager/dialog/etc stores and recalls the settings they need, which are accessible to each other module/function/etc, but are otherwise decoupled from one another.
mauronen wrote:Clearly the scenario could be much more complex as i described in Component Diagram. However the purpose is:
- decouple a wide process into smaller and simpler processes
Check (though sometimes it’s not convenient.)
mauronen wrote:- design interfaces so that each subsystem could easily cooperate each other
Cooperation is via functional programming, so the developer just needs to check the function arguments and return values, and probably any comments left by the developer responsible.
mauronen wrote:- hide subsystem's complexity, again, with a well-know interface that cooperate with corresponding plugin
I do this wherever possible, but not so as to compromise flexibility. Bearing in mind that I've got a huge amount of plugin programming ahead of me, I'm shaping the API to make that as easy as possible.
mauronen wrote:Thanks to this 2 patterns is possible to:
1) Aaron could switch Filters
2) Aaron could add or delete Filters
3) Aaron could enable or disable plugin/filter pair
4) An external developer could change a plugin or create another new (according to plugin interface)
5) Aaron could change a FilterManager (accroding to filter interface)
The goal will be to allow
anyone to switch filters. With a suitably well designed plugin, it will be possible for non-developers to switch filters (i.e. click and drag).
mauronen wrote:aaron wrote:Presently I have two classes of plugins; a general plugin, and a file I/O plugin. The only difference is that file I/O plugins expose a few functions to load/save map files (ExtSaveMapFile, etc.), but are otherwise the same as general plugins.
Try to see CPI as general plugin interface and I/O plugin interface as CPFII/CPFEI pair, that are subinterfaces of CPI. So, any other CPI-compliant interface could be a subinterface of CPI that could be enabled/disabled.
I see your point, but I think this is a bit too module-centric. As I said up above (I think), the paradigm is really FP rather than OOP. Modules are just a means to put functions into the API, so rather than disabling a module’s interface (or a part thereof), you disable the functions it has exported. That said, I don’t yet have a mechanism by which functions may be disabled, but I’ll add it should the need arise (a plugin could do this anyway.)
Anyway, thanks again for the insight. As you can probably guess, I haven’t done much (formal) work with patterns, so I find it quite interesting to see your perspective. This sort of planning might come in very useful in instances where OOP turns out to be the dominant paradigm, but I think we’ll have to wait and see what and where this happens. Thanks again for your insight.
Cheers,
Aaron.