What's new in core7?

A place to discuss everything about the Orxonox framework.

Moderator: PPS-Leaders

Post Reply
User avatar
x3n
Baron Vladimir Harkonnen
Posts: 810
Joined: Mon Oct 30, 2006 5:40 pm
Contact:

What's new in core7?

Post by x3n » Mon Oct 05, 2015 8:49 pm

Yesterday I merged the core7 branch back to trunk after almost six months of (interrupted) development. Not because it is completely "done", but rather because the new PPS is about to start and I wanted the new branches to use the updated framework features. I'll have to add some additional tests and maybe it needs some fine tuning as well.

Nevertheless, the merging of core7 brings quite a few changes to the orxonox framework. Here's a short summary.

By the way, we now have PLUGINS in Orxonox!!! :beerchug:
But let me first introduce the basics...

Static initialization

Our framework uses quite a bit of static initialization, e.g. RegisterClass(MyClass) which is used to register the class in the framework (i.e. creates an Identifier and a Factory). The way we use it, static initialization has a lot of benefits. But in general, static initialization is a bad thing because it happens before main() and the order of initialization is largely undefined (or at least not under the programmer's control).

If we use static initialization, it should have as little side effects as possible. So far, we did not really adhere to this rule in Orxonox. RegisterClass(MyClass) created an Identifier and registered it in IdentifierManager, which requires that IdentifierManager also exists at this point. Even worse than static initialization is static deinitialization. Stuff gets destroyed in (almost) random order and you never know if you survive it.

With core7, static initialization is a lot cleaner. Every static call like RegisterClass(), CreateConsoleCommand(), registerStaticNetworkFunction() etc. will now only create an independent object and put it into a queue (the list of statically initialized instances in the current module). Only when the framework is fully loaded, these instances are added to their managers (IdentifierManager, ConsoleCommandManager, etc.).

And the best thing about this is: It is reversible. Every module keeps track of the list of statically initialized instances and can unload them when they are not needed anymore (e.g. when the game is shutting down). This means that not only initialization, but also deinitialization is now fully controlled by the framework.


Plugins

The logical consequence of the above point is that we now have plugin support in Orxonox. Since every module can be loaded and unloaded in a controlled manner, we can do this at runtime. This means that (almost) every independent module can be loaded/unloaded during the game. As soon as it is loaded, clases, console commands, etc. become available in the game. As soon as it is unloaded, all instances are destroyed and the features of the plugin are not accessible anymore.

How to use a plugin in Orxonox (short version):
  1. Mark the module as "PLUGIN" in CMakeLists.txt
  2. Add the plugin to your level
  3. Watch the plugin being loaded when the level starts
  4. Watch it being unloaded when the level ends
plugins.PNG
plugins.PNG (165.55 KiB) Viewed 20845 times
Delayed destruction

The framework now allows destroyDelayed() in addition to the already existing destroy(). The problem with destroy() sometimes is that it deletes the instance immediately (as long as no smart/strong pointers are pointing to it). In some situations this is unwanted, e.g. if the object is destroyed as a consequence of a collision. In this case, Orxonox will crash because the object is destroyed while bullet ist still using it.

This led to strange workarounds like PawnManager (which did nothing else than destroying dead pawns in the next tick) or even stranger ShipPartManager (which did the same to ship parts).

destroyDelayed() introduces the same functionality as a framework feature. Instead of immediately destroying the instance, it merely gets marked as 'to be destroyed'. As soon as the current tick finishes, the instance will be actually destroyed (i.e. destroy() is called).

Note that there is a) no guarantee for the order of destruction and b) no guarantee that a destroyed object is actually deleted (because it may be kept alive by a smart/strong pointer).


Strong Pointer

So far we had SmartPtr and WeakPtr in the Orxonox framework. The confusing thing was that WeakPtr is actually also smart (in contrast to a "dumb" plain C pointer). This is the reason why I renamed SmartPtr to StrongPtr. It's still the same class and has still the same functionality, but it's naming is now easier to understand.

The new guideline reads as follows:
There are StrongPtr and WeakPtr. StrongPtr keeps the object alive, WeakPtr doesn't. Both are Smart-Pointers in the sense that they will either point to an existing object or to NULL, but never ever to an invalid address ("dangling pointer").


Runtime checks

There are some new runtime checks. One part of the checks tries to validate the class hierarchy, i.e. tries to find missing calls to RegisterClass, RegisterObject or situations where the manual definition of the inheritance of an abstract class is wrong.

Another check tries to find situations when collision shapes are destroyed in a collision callback. This will often lead to crashes and is a common source of errors. With core7 this kind of errors can be a) detected and b) fixed by using destroyLater().


Compile-time checks

There's a bunch of new compile time checks to validate that certain framework features are only used with correct classes. For example, Identifiers can only be used with classes that inherit from Identifiable and ObjectLists can only be used with classes that inherit from Listable.

Another check ensures that ObjectList-iterators use the same type like the begin() and end() iterators of the corresponding ObjectList.


Cleanup

Some files were moved, renamed or split into different classes.
There are several new headers that bundle certain framework features like e.g. ConsoleCommandIncludes.h, NetworkFunctionIncludes.h, or CommandLineIncludes.h.
Fabian 'x3n' Landau, Orxonox developer

muemart
Noxonian Qulomks
Posts: 28
Joined: Fri Mar 28, 2014 1:10 pm

Re: What's new in core7?

Post by muemart » Wed Oct 07, 2015 9:37 pm

Good stuff, but is there a meaningful distinction between plugins/modules/the "core", or is it just a way to express how specialized the code is (e.g. the pong plugin is only useful for the pong level)?

User avatar
beni
Baron Vladimir Harkonnen
Posts: 949
Joined: Tue Oct 03, 2006 9:15 am
Location: Zurich
Contact:

Re: What's new in core7?

Post by beni » Thu Oct 08, 2015 1:41 pm

Awesome x3n. Thanks for the contribution and for explaining the changes so clearly. Even I understood what's going on :P

I have the same question as muemart. We have modules, libraries and plugins and sometimes the distinction doesn't really make sense (also not when looking at the directory tree). Sometimes it's not clear if I have to look for a class in a library or in a module or somewhere else entirely. If there would be some semantic, clear rule for this, I think it would help people to work with this project that is already very large.
From what I see from your example is that plugins must be defined in the level file. Does that mean that plugins should be level specific? When does it make sense to make a plugin a part of a module or library?

I love that core7 solves a lot of problems we had we our comfort functionalities like Identifiers and SmartPointers.

Solving destroy() was about time. Great solution!

I'm looking forward seeing how you implemented those checks that classes are used properly. Quite an intelligent system I'm sure ;).

Thanks again for the work and the explanation.
"I'm Commander Shepard and this is my favorite forum on the internet."

User avatar
x3n
Baron Vladimir Harkonnen
Posts: 810
Joined: Mon Oct 30, 2006 5:40 pm
Contact:

Re: What's new in core7?

Post by x3n » Thu Oct 08, 2015 9:01 pm

The compile time checks were implemented with static assertions from the boost library:

Code: Select all

    template <class T>
    class ObjectList
    {
        BOOST_STATIC_ASSERT((boost::is_base_of<Listable, T>::value));
Rather simple ;)


About libraries, modules, and plugins:

So far we only distinguished between "libraries" and "modules". Technically they are both shared libraries, but "libraries" are linked at compile time and "modules" at runtime. The "libraries" define the framework; we have external (ogre, boost, bullet, ...) and internal (util, core, network, ...) libraries. There is no game logic in these libraries, it's only framework.

Then there is the orxonox library. It depends on the framework libraries and defines the basics of the game: different gamestates and base classes like level, scene, gametype, worldentity, pawn, etc.

Beside the orxonox library there is the orxonox executable which is simply a main()-function that calls the orxonox library.

Modules on the other hand depend on the orxonox library and inherit all framework and game functionality. Modules are loaded at runtime by the framework when it is initialized. Modules contain specialized game logic and they are usually independent of each other albeit it's possible to define dependencies between modules.

For example, the pong and tetris modules are independent of each other, but they both depend on the overlays module.

Library-dependency in Orxonox:
orxonox-libraries-overview.png
orxonox-libraries-overview.png (12.3 KiB) Viewed 20827 times
Plugins finally are just a special kind of modules. Technically they are absolutely identical, but they are treated differently by the framework. When the framework is initialized, it will always load all modules, but it will not load any plugins. So if you created a new class or a new console command and you placed it inside a module, you are guaranteed that the class or the console command is known to the framework and ready to use.

However, if you placed your code in a plugin, it is not accessible at the beginning. You can not load the class (e.g. through XML) nor can you execute the console command - it doesn't exist yet. First you need to load the plugin. You can do this manually with 'PluginManager load <name>' in the console. Now the plugin is loaded by the framework and the class/consolecommand becomes accessible in the game.

Because it's rather inconvenient to always load plugins manually I added the possibility to bind one or many plugins to the scope of a level. This means that these plugins are automatically loaded together with the level (and also unloaded again). This is what you saw in my first post (plugins = "pong" in pong.oxw: it means that the pong plugin is automatically loaded if you start the pong level).

Plugins are not necessarily bound to a level, but so far most modules/plugins contain gametypes, so I added a convenience feature to load a plugin together with a level. You could also define debug functionality in a module and load it on demand (e.g. a debug overlay). You could basically convert every module to a plugin, but you need a way to load it before you use it. If you're unsure what to use, use a module.

Runtime behaviour of libraries in Orxonox:
orxonox-libraries-runtime.png
orxonox-libraries-runtime.png (9.83 KiB) Viewed 20827 times
(Modules are loaded sequentially by the framework at runtime - and plugins are loaded on demand)

You may ask why I added plugins to the framework. Honestly: simply because I could. When I started improving the initialization of the framework I soon realized that this would allow me to load and unload (and thus reload) modules at runtime. There is no immediate benefit, but I like the idea and it's proof of a clean design of the framework.

Now that we have plugins, we can start thinking about opportunities. Orxonox grows and there's more and more code that needs to be loaded and initialized when Orxonox is started. Since plugins are not loaded by default, it may theoretically improve the startup time.

It also means that you can unload a plugin, re-compile it and load it again. You can alter the game without even closing it. This feature could be especially useful with regard to an upcoming editor mode in Orxonox.

By the way, you asked how to find a class in the source repository?
You probably won't like the answer, but it's CTRL+SHIFT+R (for source files) and CTRL+SHIFT+T (for classes and types) in eclipse. Seriously, it's the IDE's job to find your files. Libraries (or modules/plugins) are used to keep independent code separated as much as possible.
Fabian 'x3n' Landau, Orxonox developer

Post Reply

Who is online

Users browsing this forum: No registered users and 8 guests