3

I'm working on a game framework and have decided to use OSGi to bind it all together, as modularity is core to the game itself.

I want the game to be run as an all-in-one bundle on someone's machine, or writing it as a server, putting the login server on an Azure instance, and having the game engine running on an AWS instance, or running the whole thing from a Raspberry Pi.

When I was working on this before, I split up the work into different services. I had a GameEngine, a LoginServer, a 2DGameClient, etc. However, these were all wired up in a single jar, with a single Main class, that looked something like

public class Main {

    public static void main(String[] args) {
        final ExecutorService threadPool = Executors.newFixedThreadPool(3);

        final Runnable gameEngine = new GameEngine();
        final Runnable loginServer = new LoginServer();
        final Runnable gameClient = new 2DGameClient();

        threadPool.submit(gameEngine);
        threadPool.submit(loginServer);
        threadPool.submit(gameClient);

        // ...
    }
}

Where each service was its own thread inside one shared thread pool.

However, now that I've decided to try to design it around OSGi from the ground up, I'm wondering how to make it as modular as I'd like.

What I'm thinking so far is that at a minimum, I'll need 3 bundles per service, for example

  1. Game Engine API
  2. Game Engine Provider
  3. Game Engine Activator

However, I feel that most of the activator code would be essentially

public void start(BundleContext context) {
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    threadPool.submit(new Runnable() {
        public void run() {
            GameEngine engine = FindRegisteredGameEngine();

            engine.start();
            // ...
        }
    });
}

But then I can't wait on the result of the submit call, because according to the documentation start is supposed to return as soon as possible.

Knowing that I want the services to be able to be deployed as where ever as possible, should these service providers provide themselves as Runnable objects? If there are some preconditions that must be met before a service can run (database connection, config files read, etc) is it then the responsibility of the run method? Is there a right way to do this in the OSGi framework?

2

First of all: I would recommend to use Declarative Services (DS) in combination with bnd as build tool which is the current state of the art in OSGi, instead of bundle Activators. Here is a very good blog post that should provide you with the basics needed to get started with DS: http://blog.vogella.com/2016/06/21/getting-started-with-osgi-declarative-services/ and also OSGi enRoute should provide you with a setup to get started in Eclipse or any other IDE that supports Maven https://enroute.osgi.org/tutorial/020-tutorial_qs.html

If you are using DS you usually have two bundles per service, e.g., an API bundle that contains all interfaces, abstract classes, data types you need for your implementation AND an implementation bundle that contains the actual implementation.

For example the game engine API bundle would contain an interface GameEngineService and the implementation bundle would contain a class 2DGameEngine that implements this interface.

Regarding the aggregation of all your services you could create one additional main implementation only bundle (no api bundle required) that contains a Main component, that references all the services you need to assemble your game framework.

For a better understanding I sketched some classes and interfaces below:

your.domain.main.impl Bundle

@Component
public class Main {
  @Reference
  private GameEngineService engine;

  @Reference
  private LoginServerService login;

  @Reference
  private GameClientService client;

  private ExecutorService threadPool;

  @Activate
  private void start(){
    threadPool = Executors.newSingleThreadExecutor();
    threadPool.submit(engine);
    threadPool.submit(login);
    threadPool.submit(client);
  }

  @Deactivate
  private void stop(){
    threadPool.shutdown();
  }
}

your.domain.engine.api Bundle

public interface GameEngineService extends Runnable{
  // some methods
}

your.domain.engine.impl Bundle

@Component
public class 2DGameEngine implements GameEngineService {

  // e.g., if you need a db service to be present for this component to be usable
  @Reference
  private DatabaseService db;

  // implementation of interface
  ...

}
1

The approach from @Sandared works for sure but I would suggest another approach: I would just have every Service spawn its own Thread in the activation Method, probably in another Bundle, to take the Example from above

@Component
public class 2DGameEngine implements GameEngineService {

  // e.g., if you need a db service to be present for this component to be usable
  @Reference
  private DatabaseService db;

  // implementation of interface
  ...

}

I would add another Service "the Runner" like

@Component
public class 2DGameEngineRunner {

  // e.g., if you need a db service to be present for this component to be usable
  @Reference
  private 2DGameEngine engine;

  private Thread thread;

  // Spawn the Thread
  @Activate
  void activate() {
    thread = new Thread(engine);
    thread.setDaemon(true);
    thread.start();
  }

  @Deactivate
  void deactivate() {
    // Stop the Thread here Gracefully...
  }

}

This approach has the advantage that you can start and stop all of the services in a controlled manner and independently so less coupling and more modularity.

Of course one could also have a central ThreadPool (wrapped as DS) but I would really suggest to activate / deactive them modular in a separate Service / Bundle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.