Not logged in, Join Here! or Log In Below:  
 
News Articles Search    
 

 Home / Game Design & Programming / My sloppy engine design.. Account Manager
 
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.
 
cypherx

November 16, 2004, 06:01 PM

I've written a game engine (somewhat based on the Enginuity tutorials, thanks superpig) that I'd like to make more OOP-proper.

Every subsystem of the engine inherits from the Task interface:

class Task
{
void Init()=0;
void Update(World* world)=0;
}

The World class:
class World
{
vector objects;
}

class Object
{
vector2d position;
vector velocity;
RGB color;
}

This is how Tasks are used in the code:
PhysicsTask physics;
RendererTask graphics;
TaskManager.InsertTask(physics, 10); //update every 10 ms
TaskManager.InsertTask(graphics, 10);

I wrote a simple 2d ball collision simulation, and for that purpose this setup worked fine. Now...I'd like to use the same scheduling scheme (tasks running in a TaskManager) for other programs...but I'm constained by the Task::Update funtion taking a World pointer...
This limits the task to only be useful for manipulating game objects...

How do I restructure the Tasks/TaskManager to still work in the game, but also be useful for other kinds of programs?

Thanks,
Alex

 
I'M BRIAN FELLOWS

November 17, 2004, 12:38 AM

CHANGE THE PARAMETER IT TAKES.

 
Mil_Gov

November 17, 2004, 12:53 AM

either you could have overloaded methods or maybe use a void* attribut:

class Task
{
void Init()=0;
void Update(void* someData)=0;
}


i don't know if that is considerd bad design or something, i've never used it myself...

The best solution (i think) would be to structure it something like:

class Task
{
void Init()=0;
void Update()=0;
}

class GameTask: public Task
{
void Update(World* world)=0
}

something like that...

 
MadHed

November 17, 2004, 06:26 AM

I can't see why you're passing the World* pointer to the task.
Shouldn't it rather be:

  1.  
  2. class PhysicsTask : public Task
  3. {
  4.   void Init()
  5.   {
  6.     ...
  7.   }
  8.  
  9.   void Update()
  10.   {
  11.     TheWorld.MoveObjects(Timer.GetDelta());
  12.   }
  13. }
  14.  
  15. and
  16.  
  17. class RenderTask : public Task
  18. {
  19.   void Init()
  20.   {
  21.     ...
  22.   }
  23.  
  24.   void Update()
  25.   {
  26.     TheWorld.RenderObjects(&D3DRenderer);
  27.   }
  28. }
  29.  

or similar?

 
MadHed

November 17, 2004, 06:37 AM

Ha something comes to my mind... =)
You could overload your task constructor so you cann pass all relevant information at creation time. The task doesn't change anyway.

  1.  
  2. class RenderTask : public Task
  3. {
  4. private:
  5.   Renderer* renderer;
  6.   World* world;
  7.  
  8. public:
  9.   RenderTask(World* w, Renderer* r)
  10.   {
  11.     world = w;
  12.     renderer = r;
  13.   }
  14.  
  15.   void Init()
  16.   {
  17.   }
  18.  
  19.   void Update()
  20.   {
  21.     if (renderer && world) renderer->RenderObjects(world);
  22.     //or world->RenderObjects(renderer);
  23.   }
  24. }
  25.  


Hmm that looks nice, why didn't I implement this? ;)

 
cypherx

November 17, 2004, 12:38 PM

Mil_Gov:
I'd rather not use a void* parameter...it kills type-safety, and I'm afraid of the evil bugs that'll crop up on its account.

As for the second solution: Is changing the paramter list of a virtual function OK?
I thought the idea behind inheritance was that all derived classes have identical functions as the base class.

In TaskManager's main loop I have code similar to the following:

  1.  
  2. for each Task:
  3.    task.Update(world)
  4.  


-Alex

 
cypherx

November 17, 2004, 12:48 PM

MadHed:
Thanks for the reply...but my problem is the inflexibility of having a World* parameter, whether that's in the update function or the constructor. I'd like to use the task scheduler for non-game applications (specifically, I'm coding a neural network simulation with a visualizer)...so I need something more generic than a World pointer to pass to a Task...but what should that something be? (I've already ruled out void* to maintain safety, but I can't think of anything else).

Ideally I'd like to be able to do things like the following:

  1.  
  2.  
  3. class NeuralNetwork { /* neural network of some sort contained here */ };
  4. class GameWorld ( /* positions in a tiled 2d world */};
  5.  
  6. Task NeuralTask;
  7. Task GameTask;
  8.  
  9. TaskManager worldmanager (GameWorld);
  10. TaskManager neuralmanager (NeuralNetwork);
  11.  
  12.  


Ok, I so I guess a better way to say what I want:
I'd like to seperate a State from the code that acts upon it (or reacts to it)...and I'd like to make that State generic, so it might be a Game State (containing game objects with vect2d position, velocity, etc..) or a NeuralNetworkState (containing neurons with certain activations), or any other state.

-Alex

 
Sohra

November 17, 2004, 02:04 PM

Could you elaborate a little more on what exactly the Task::Update() function needs to do? I dont see exactly why it needs to take an argument. Couldn't you just derrive every class from the Task class, and it contain a pure virtual Update function with no arguments?

 
cypherx

November 17, 2004, 03:14 PM

Ummm...sorry, I guess the code snippet is incomplete.

  1.  
  2.  
  3. class NeuralNetwork { /* neural network of some sort contained here */};
  4. class GameWorld ( /* positions in a tiled 2d world */};
  5.  
  6. Task NeuralTask;
  7. Task GameTask;
  8.  
  9. TaskManager gamemanager (GameWorld);
  10. TaskManager neuralmanager (NeuralNetwork);
  11.  
  12. worldmanager.InsertTask(GameTask);
  13. neuralmanager.InsertTask(NeuralTask);
  14.  
  15. worldmanager.Run();
  16. neuralmanager.Run();
  17.  
  18.  

 
cypherx

November 17, 2004, 03:32 PM

Here were the things I wanted for the game:
1) A world state containing the positions, velocities, etc... of all game objects
2) A world state Factory to generate world states from a description file
3) The ability to switch world states mid-game (load a new map) without having to tell the tasks.

Now I want to reuse the TaskManager for a neural network simulator, in which I want to:
1) Store neural network data (the activations of nodes, their interconnection weights) in a NetworkState
2) Have a factory to generate the NetworkState from a file
3) Be able to switch between network states while running the simulation

----

Maybe I'd be better off having each Task hold on to a pointer of the State (world state for a game)...and update that state when I load a new level...

Would something like this be OK?


  1.  
  2. class GameWorld ( /* positions in a tiled 2d world */};
  3.  
  4. GameWorld.loadLevel("level.file");
  5.  
  6. RenderTask graphics(&GameWorld);
  7.  
  8. TaskManager manager;
  9. manager.InsertTask(graphics);  
  10. manager.Run(); //calls Update() on graphics
  11.  
  12. GameWorld.loadLevel("newlevel.file");
  13.  
  14. manager.Run(); //graphics->Update() renders new world state
  15.  


The only thing that bugs me about this...every Task needs to be initialized with the GameWorld it operates on...this seems redundant since every Task in the TaskManager will deal with the same GameWorld.

-Alex

 
Mil_Gov

November 17, 2004, 03:37 PM

ok, i think i get your point. In your mainpost you said that you would like to have it something like this:

  1.  
  2. PhysicsTask physics;
  3. RendererTask graphics;
  4. TaskManager.InsertTask(physics, 10); //update every 10 ms
  5. TaskManager.InsertTask(graphics, 10);
  6.  


that means you could have a structure like this, even if you didn't like the void* pointer i think this is safe enought...

  1.  
  2. class Task{
  3. public:
  4.   Task(void* world):myWorld(world){}
  5.   virtual void Update()=0;
  6.   void* myWorld;
  7. };
  8.  
  9. class PhysicsTask: public Task{
  10. public:
  11.   PhysicsTask(PhysicsWorld* world):Task(world){}
  12.   void Update(){ myWorld.doPhysics(); }
  13.   PhysicsWorld* myWorld;
  14. };
  15.  
  16. class RenderTask: public Tast{
  17. public:
  18.   RenderTask(RenderWorld* world):Task(world){}
  19.   void Update(){ myWorld.doRender();}
  20.   RenderWorld* myWorld;
  21. };
  22.  


do you think this would work for you? you never actually have to convert the void* pointer yourself because you are not going to create a Task instance only PhysicsTask and RenderTask instances...

edit: bummer didn't see your new post further down...you got a hard one here...

 
Sohra

November 17, 2004, 04:41 PM

Hey Alex

What if you were to template your TaskManager class? Then you could have one generic manager class that could become a manager for a neural net, or a game world... I dont know if you know much about templates or not, if you dont, I'll try help you out with that... but you could define your taskmanager class along the lines of:

  1.  
  2. template<class T> class TaskManager {
  3.  public:
  4.   void InsertTask(Task*);
  5.  private:
  6.   T centerofmylife;
  7. };
  8.  


Then you can do something like:

  1.  
  2. TaskManager<World*> gmanager;
  3. TaskManager<NeuralNet*> nmanager;
  4.  
  5. RenderTask graphics;
  6. NeuralTask neuralnet;
  7. gmanager.InsertTask(&graphics);
  8. nmanager.InsertTask(&neuralnet);
  9. gmanager.centerofmylife->LoadFile("/worlds/map1.wld");
  10. nmanager.centerofmylife->LoadFile("/nets/eliza.net");
  11. gmanager.Run();
  12. nmanager.Run();
  13.  


And the template TaskManager::TaskManager() constructor does something like... centerofmylife = new T;

Is this what you want?

 
MadHed

November 17, 2004, 06:20 PM

Yeah the templated version could do the "trick".
However, you're restricted to changing only one element (centerofmylife).

The functionality of the basic Task class (it's just an interface, isn't it) is only:

-- Init() //Initializes the Task (whatever that might be...)
-- Update() //Updates the task (Whatever that might be...)

You could do anything you want with it

Now you can derrive new Classes from this base class.

  1.  
  2. Class StatusTask : public Task
  3. {
  4. public:
  5.   void Update()
  6.   {
  7.     cout << "status is blah!" << endl;
  8.   }
  9. };
  10.  
  11. Class RenderTask : public Task
  12. {
  13. public:
  14.   RenderTask(some stuff)
  15.   {
  16.     //save "some stuff"
  17.   }
  18.  
  19.   void Update()
  20.   {
  21.     //Do something with "some stuff"
  22.   }
  23. }
  24.  


Why is that in any way restricting the use of your Task class?
You can still derive new classes from Task for whatever purpose they are needed. =)

So when you need a statud Task you do:

Task status = new StatusTask();
TaskManager.Add(status);

when you need a RenderTask you do:

Task render = new RenderTask(blablabla);
TaskManager.Add(render);

From the TaskManager's point of view both are just "Task"s. But your application is responsible for creating the tasks and can init them in any way. =)

 
MadHed

November 17, 2004, 06:27 PM

Oh! I think you misunderstood the concept of the TaskManager!!!!

You only have 1! TaskManager for all you different Tasks.
What would be the point in a Taskmanager anyway? ^^

  1.  
  2. TaskManager manager;
  3.  
  4. Task logic = new LogicTask(.....);
  5. Task physics = new PhysicsTask(....);
  6. Task render = new RenderTask(....);
  7. Task timer = new TimerTask(.....);
  8.  
  9. manager.InsertTask(logic);
  10. manager.InsertTask(physics);
  11. manager.InsertTask(render);
  12. manager.InsertTask(timer);
  13.  


However, I haven't implemented a taskmanager myself. It just seemed too overkill for my engine. It's using a good ol' Harcoded Mainloop.
---> http://madhed.ma.funpic.de

 
MadHed

November 17, 2004, 06:45 PM

Yo have, sort of, answered your own question, sort of... =)

You mean it's redundant to give every task the world pointer it operates on since they are all operating on the same world.
In your original code you passed the world pointer as an argument to Update(World* world). Isn't this redundant also, since all tasks are dealing with the same world...

There are several different ways to acomplish this.
1.
stick with it. Having an extra world pointer in each worl task isn't that bad.
Pro's:
-You can have two tasks of the same type running on different worlds.
e.g one for the client the other for the server world.
-It's clean
Con's:
You are spending an extra 4 bytes on every task of which you might have...like...1...2?!

2.
Add another layer of abtraction to your class and add a static World pointer:

  1.  
  2. Class BaseWorldTask : public Task
  3. {
  4.   static World* worldPtr;
  5.  ....
  6. }
  7.  
  8. class PhysicsTask : public BaseWorldTask
  9. {
  10.   void Update()
  11.   {
  12.     //do something with worldPtr
  13.   }
  14. }
  15.  
  16. class RenderTask : public BaseWorldTask
  17. {
  18.   void Update()
  19.   {
  20.     //render worldPtr
  21.   }
  22. }
  23.  
  24.  
  25. TaskManager manager;
  26.  
  27. myWorld = new World();
  28. BaseWorldTask::worldPtr = myWorld;
  29.  
  30. PhysicsTask phys = new PhysicsTask();
  31. RenderTask render = new RenderTask();
  32.  
  33. manager.InsertTask(phys,10);
  34. manager.InsertTask(render,10);
  35.  
  36.  

 
cypherx

November 17, 2004, 08:42 PM

Sorry...I guess I created some confusion. I'm working on two distinct programs...I just put the two taskmanagers in the code together to show that each task manager held a collection of tasks working with unique states (one set of tasks deals with a world state, the other with a neural network state).

-Alex

 
Joakim Hårsman

November 18, 2004, 05:31 AM

Why can't you just make the update method take no arguments and have the various tasks get the info they need at creation time? Seems simple enough. Oh, and it might make sense to pass in the update frequency since you might need compensate for this in code. Otherwise, updating the physics more often will cause things to go faster which isn't what you want.

 
This thread contains 17 messages.
 
 
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.