Guides/Platform Development/Tutorials/Application Structure

From Sirikata Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Structure of a Sirikata Application: Contexts, Services and Plugins

In order to add new components to the system, and in some cases when creating a plugin implementing an existing interface, you'll need to understand how a Sirikata application (like space or cppoh) is structured. This document walks you through the process of setting up a new application. Although this will be unusual -- you would only follow this process directly to add a new binary for a completely new service -- it is useful to provide context for how components you write will fit into an application.

Sirikata provides a basic framework for constructing applications which provides common infrastructure, for example managing the event loop, managing the lifetime of the application, some simple timing functions, and managing the services running in the application.

Definitions

To begin, we start with some definitions. These should give a sense of what each one does, but the rest of the tutorial will fill in the details.

A Barebones Application

A barebones application is simple to setup: simply create a Context to run the application and start it. When the application finishes running, it will return and we just need to clean up.

First, start by including the necessary headers:

#include <sirikata/core/service/Context.hpp>
#include <sirikata/core/network/IOServiceFactory.hpp>
#include <sirikata/core/network/IOService.hpp>
#include <sirikata/core/trace/Trace.hpp>

Then create the main method and create the Context , filling in a few parameters

int main(int argc, char** argv) {

    Network::IOService* ios = Network::IOServiceFactory::makeIOService();
    Network::IOStrand* mainStrand = ios->createStrand();

    Trace::Trace* trace = new Trace::Trace(trace_file);
    Time epoch = Timer::now();

    Context* ctx = new Context("OurApplication", ios, strand, trace, epoch);

The first two lines setup the basic event handling system and these objects are stored in Context to make them available throughout the system. The Trace and epoch values are for collecting statistics and controlling timed applications.

At this point, the context has almost enough information to run. The Context is itself a Service that will be added to itself to get notified of the run starting and ending. Then we'll start the application by calling run.

ctx->add(ctx);
ctx->run(1);

The parameter to the run method is how many threads to run with. Sirikata supports running with many threads to exploit as many cores as are available on the system. Events will be dispatched and handled on multiple threads. To ensure serialization of certain event handlers, see IOStrand or use locks directly.

Finally, when everything has stopped running and we just need to clean up and close the main method.

ctx->cleanup();
trace->prepareShutdown();

delete ctx;

trace->shutdown();
delete trace;

delete mainStrand;
Network::IOServiceFactory::destroyIOService(ios);

return 0;

}

And that's it. This should be sufficient to get a running application -- but it won't do anything except wait for shutdown to be requested (which you can do via a signal, hit Ctrl-C).

Loading Plugins

In order to use implementations of interfaces that are not included in the libraries, you must load a plugin. For example, there is not an implementation of the ObjectSegmentation interface provided directly in the libspace library. One implementation, LocalObjectSegmentation is implemented in the space-local plugin and is used to run a single space server system where only a local table is required for the ObjectSegmentation.

In our hypothetical application, let's assume we need an ObjectSegmentation instance and we know ahead of time that we want the LocalObjectSegmentation . First, we'll need to add the header that brings in the interface and its factory, OSegFactory . Because we'll need to load the appropriate plugin, we'll also need to use the PluginManager class.

#include <sirikata/space/ObjectSegmentation.hpp>
#include <sirikata/core/util/PluginManager.hpp>

Then, after the creation of our Context , we need to first load the plugin to make the implementation available to the OSegFactory , and then instantiate a copy through the factory.

PluginManager plugins;
assert( ! OSegFactory::getSingleton().hasConstructor("local") );
plugins.load("space-local");

assert( OSegFactory::getSingleton().hasConstructor("local") );
ObjectSegmentation* oseg = OSegFactory::getSingleton().getConstructor("local")(ctx, ctx->mainStrand);

On the last line, the parameters are not important; the key point is that we retrieved the constructor for the "local" implementation of ObjectSegmentation . That's it -- we dynamically loaded the plugin, making this local implementation available, and instantiated it. The assert statements show what the call to PluginManager::load() changed: the "local" constructor became available.

Of course we'll need to clean this up along with the other objects.

delete oseg;

Adding a Service

Of course, the ObjectSegmentation we created isn't connected to anything and won't be notified of the start and end of the application. Knowing how to connect the component depends on the particular component, but adding the component to the application's context is easy

ctx->add(oseg);

ObjectSegmentation implements the Service interface, so it will now be notified with start() and stop() method invokations.

Conclusion

Now that you know the basics of a Sirikata application, you should take a look at the Context, Service, and Plugin classes in the API documentation to see what else they can do. And of course the main.cpp files for each Sirikata binary is a good place to start: they are long but readable, will teach you some of the common patterns, and show how these tools are integrated with others such as the options system.