Software architecture

Discussion about everything. New games, 3d math, development tips...
Post Reply
Escen
Competition winner
Posts: 167
Joined: Sun Jul 19, 2009 11:27 am
Location: the Netherlands
Contact:

Software architecture

Post by Escen »

Hello Guys,

As my applications are growing I have the need of managing my programs.
My today's problems are more associated with program complexity rather then coding itself (for now). This makes it hard to start-up.
I have more plans to extend my applications, therefore at this point it is still possible to apply any structure software.
I don't have any experience with software architecture, so I'm wondering if anyone has any advise of choosing.

Thanks
Escen
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

I create a struct for everything, and then in main.cpp init everything and set some parametres, so it looks like this

Code: Select all

initShaders(video);

levelManager levelMgr(smgr);
weatherManager weatherMgr(smgr);
grassManager grassMgr(smgr);
treeManager treeMgr(smgr);
weaponManager weaponMgr(smgr);
UIManager UIMgr(device);
npcManager npcMgr(smgr);

levelMgr.loadLevel("./data/levels/example/level.irr");
levelMgr.setLevelFog(SColor(0,128,128,128), 200, 800);

weatherMgr.initWeather();

weaponMgr.loadWeapons("./data/config/weapons.xml");

player* player1= new player(smgr, timer);
player1->setPrimaryWeapon(weaponMgr.createWeaponFromIndex(0), camera);
player1->setSecondaryWeapon(weaponMgr.createWeaponFromIndex(1), camera);

UIMgr.setActivePlayer(player1);
UIMgr.init();

npcMgr.setTerrain((ITerrainSceneNode*)smgr->getSceneNodeFromType(ESNT_TERRAIN));
...
I don't know if it is the best solution, but works so far. But should I try multiplayer some day, I will probably run to problems with my player/npc management.
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

Ok, here is one question: I have 2 classes. Now I need to use method from first class in the second class. What is the best way to do this? I though about passing pointer on first class to second class, but with multiple classes it could get a little messy.
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
randomMesh
Posts: 1186
Joined: Fri Dec 29, 2006 12:04 am

Post by randomMesh »

You could do it like this:

You have a class Game with takes care of the IrrlichtDevice (init, rendering, dropping, etc.) and other stuff like networking, physics, etc.

Code: Select all

class Game
{

public:

bool init() { /* init device here */ }

irr::IrrlichtDevice* getIrrlichtDevice() const { return device; }

private:

irr::IrrlichtDevice* device;
Physics* physics;
};
Then you pass a pointer of this class to the other classes.

Code: Select all

class Player
{

public:

Player(Game* game) :
    game(game)

private:

Game* game;
};
Now you can access all the shared classes (device, physics, etc.) from the game pointer. All without using globals!

You need to use forward declaration for this to work. i.e. don't include the Game.h in Player.h but say:

Code: Select all

class Game; //forward declaration

class Player
{

}
and only include Game.h in Player.cpp.

Another thing you can do is to derive Game from irr::IEventReceiver and have a bool OnEvent(), so you can handle the events in your game class and access the other classes without too much hassle.
"Whoops..."
Brainsaw
Posts: 1183
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

I came to the conclusion that inter-class communication in my next project will be done using a single message queue (e.g. the "select level" dialog needs to tell the "play game" class which level and options were selected). I already have such a queue in my IrrOde wrapper (which is quite extensible btw), but I'm not sure about whether or not I should re-use this one or add another one to my state machine. It will be something like this:

Code: Select all

  #include <irrlicht.h>

using namespace irr;
using namespace core;

//the message interface to be sent
class IMessage {
  public:
    virtual u32 getCode()=0;
};

//the receiver interface that handles messages
class IMessageReceiver {
  public:
    virtual void handleMessage(IMessage *aMessage)=0;
    virtual void queueDestroyed()=0;
};

//the messaging queue
class CMessageQueue {
  protected:
    list<IMessageReceiver *> m_lReceivers;

  public:
    ~CMessageQueue() {
      list<IMessageReceiver *>::Iterator it;
      for (it=m_lReceivers.begin(); it!=m_lReceivers.end(); it++) (*it)->queueDestroyed();
    }

    void addReceiver(IMessageReceiver *p) {
      //first make sure one receiver is only added once
      list<IMessageReceiver *>::Iterator it;
      for (it=m_lReceivers.begin(); it!=m_lReceivers.end(); it++) if ((*it)==p) return;
      printf("CMessageQueue::addReceiver: adding receiver\n");
      m_lReceivers.push_back(p);
    }

    void removeReceiver(IMessageReceiver *p) {
      list<IMessageReceiver *>::Iterator it;
      for (it=m_lReceivers.begin(); it!=m_lReceivers.end(); it++) {
        printf("CMessageQueue::removeReceiver: removing receiver\n");
        m_lReceivers.erase(it);
        return;
      }
    }

    void postMessage(IMessage *pMsg) {
      list<IMessageReceiver *>::Iterator it;
      printf("distributing message\n");
      for (it=m_lReceivers.begin(); it!=m_lReceivers.end(); it++)
        (*it)->handleMessage(pMsg);
    }
};

//an implementation of the message to send a text string
class CTextMessage : public IMessage {
  protected:
    stringc m_sText;

  public:
    CTextMessage(stringc sText) { m_sText=sText; }
    virtual ~CTextMessage() { }
    virtual u32 getCode() { return 23; }
    stringc getText() { return m_sText; }
};

//The actual receiver
class CReceiver : public IMessageReceiver {
  protected:
    CMessageQueue *m_pQueue;

  public:
    CReceiver(CMessageQueue *pQueue) { pQueue->addReceiver(this); m_pQueue=pQueue; }
    virtual ~CReceiver() { if (m_pQueue) m_pQueue->removeReceiver(this); else printf("queue destroyed!\n"); }

    virtual void handleMessage(IMessage *aMessage) {
      if (aMessage->getCode()==23) {  //the code of the text message
        CTextMessage *p=(CTextMessage *)aMessage;
        printf("text received: \"%s\"\n",p->getText().c_str());
      }
    }

    //make sure we don't try to remove the receiver from a deleted queue
    virtual void queueDestroyed() { m_pQueue=NULL; }
};

int main(void) {
  //create queue, receiver and a test text message
  CMessageQueue *pQueue=new CMessageQueue();
  CReceiver *pReceiver=new CReceiver(pQueue);
  CTextMessage *pMsg=new CTextMessage("Hello World");

  //post the message to all registered receivers
  pQueue->postMessage(pMsg);

  //cleanup
  delete pMsg;
  delete pReceiver;
  delete pQueue;

  return 0;
}
I think I'll try this piece of code once I'm back home ... got no idea if it really works, but it should.

This way you can have all classes communicate with each other without the need to know each other. E.g. the "select level" dialog sends a message with the selected level and some other information to the queue, the "play game" class reads the message and prepares to load the level whereas the "show highscore" class just ignores it. I found that this is quite a flexible approach to such communication things. In my "Stunt Marble Racers" project there are a lot of classes that know a lot of other classes, and a lot of special communication methods (like "selectLevel"), and for the next project I want to get rid of that.

Edit: of course some stuff is missing in the example, it is e.g. necessary to have a "remove message receiver" method in the queue. I think I'll do some additions once I'm home.
Last edited by Brainsaw on Tue May 11, 2010 10:44 am, edited 1 time in total.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
Ulf
Posts: 281
Joined: Mon Jun 15, 2009 8:53 am
Location: Australia

Post by Ulf »

Brainsaw wrote:I came to the conclusion that inter-class communication in my next project will be done using a single message queue
That is not actually a conclusion. It is called a "decision". Yea?
I think I'll try this piece of code once I'm back home ... got no idea if it really works, but it should.
Dreaming.
This way you can have all classes communicate with each other without the need to know each other.
This could get messy!
I can hear birds chirping
:twisted:

I live in the Eye of Insanity.
Brainsaw
Posts: 1183
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

Ulf wrote:
Brainsaw wrote:I came to the conclusion that inter-class communication in my next project will be done using a single message queue
That is not actually a conclusion. It is called a "decision". Yea?
Yes, after messing around with the call-method communication method I made the decision that next time it's going to be a message queue.
Ulf wrote:
I think I'll try this piece of code once I'm back home ... got no idea if it really works, but it should.
Dreaming.
I'll see this afternoon or evening ;)
Ulf wrote:
This way you can have all classes communicate with each other without the need to know each other.
This could get messy!
[/quote]

Well ... it would be possible to have e.g. send one message directly to another class, but using a message distributor is imho a good idea. I use this architecture in my IrrODE wrapper quite a lot, and it is still quite clean - I just missed using it for most of the other communication when it came to my project.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
Brainsaw
Posts: 1183
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

Just tested my code. Did (almost) work. A missing semicolon, and some typos. Updated my original post ;)
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

Well thanks everyone for ideas, but as much as I hate it, I will have to use globals(but only 3 of them! :) ). I just don't want to rewrite approx. 1/3 of my code.. I will use this approach in my next project :wink:
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

randomMesh wrote:...
Ok, I have a little problem with that. Here is what I do:
this is how the architecture looks like
#include "engine.cpp"
#include "renderer.cpp"

and in engine.cpp the class from renderer.cpp(class Crenderer) is needed to be used. so in engine.cpp, I do

Code: Select all

class Crenderer; //forward declaration
but code in engine.cpp still does not know about Crenderer class(left of '->render' must point to class/struct/union/generic type)

So what is the right way do do this?
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
Dorth
Posts: 931
Joined: Sat May 26, 2007 11:03 pm

Post by Dorth »

Hmm, any reason you include the .cpp and not the .h?
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

hm, I dont know, maybe I'm wrong there. I don't have .h's, just .cpp's.

Edit: oh, right, now I finally understand how to do forward declaration of class :wink:
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
ent1ty
Competition winner
Posts: 1106
Joined: Sun Nov 08, 2009 11:09 am

Post by ent1ty »

OK, there seems to be another problem. When I forward declare class MyClass, I can now use that class, but I still can't use methods from it. How do I make also methods available? In other words, class A needs to use methods from class B and class B methods from class A.
irrRenderer 1.0
Height2Normal v. 2.1 - convert height maps to normal maps

Step back! I have a void pointer, and I'm not afraid to use it!
ArakisTheKitsune
Posts: 73
Joined: Sat Jun 27, 2009 6:52 am

Post by ArakisTheKitsune »

ent1ty wrote:class A needs to use methods from class B and class B methods from class A.
Ummm that is not possible. Maybe to create base class with those functions and to make A and B inherit that base class or to make class C with those functions?
Knowledge is power, understanding is wisdom.
Post Reply