Kernel aproach for game engine.

Started by
23 comments, last by Kazade 18 years, 1 month ago
Quote:Original post by oggialli
Just loading dll/.so's on demand will do the trick just as well... it's really nothing java specific and NOT related to this design pattern here.


Oh, please! Hahahaha, this one I'll leave you to find the answer by yourself: www.google.com .

Let me check the logic behind it:

- No, I don't want to use the fastest and most modern VM today with the richest tool set and community support. Instead I will:
- Rewrite everything from scratch somewhere else to perform what the VM would but worse;
- Reinvent the wheel with "cool" designs that are available somewhere else already, to do similar things but worse;


Advertisement
I know there are some pretty glaring bugs and/or design flaws in the Enginuity articles. The author has said a few times he wished he'd never written the articles... Keep that in mind when you read the articles. There are plenty of clever ideas in there, but there are some silly ones too.
Quote:Original post by Anonymous Poster
Quote:Original post by oggialli
Just loading dll/.so's on demand will do the trick just as well... it's really nothing java specific and NOT related to this design pattern here.


Oh, please! Hahahaha, this one I'll leave you to find the answer by yourself: www.google.com .

Let me check the logic behind it:

- No, I don't want to use the fastest and most modern VM today with the richest tool set and community support. Instead I will:
- Rewrite everything from scratch somewhere else to perform what the VM would but worse;
- Reinvent the wheel with "cool" designs that are available somewhere else already, to do similar things but worse;
You must be from the future, because what you're describing doesn't sound like the version of Java we have in 2006.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
I didn't say you shouldn't use Java, Mr. Anonymous Coward. I simply stated Java isn't the only platform offering such functionalities. Moreover, dynamic loading of functionality doesn't have anything to do with the design pattern described here.

As you went for the "your opinion sucks" way without backing it up in any way, I might as well do the same. So I say:

Java sucks big time. The language is a horrible simplification of REAL programming languages like C++. The standard library interfaces are horrible. And of course everyone else should think the same way about Java as I do! Including you, Mr. Anonymous.

See now?

Again, I'm sorry for talking offtopic here. Regarding the design pattern in question, it is a fairly common way of doing things not just in game engines but overall in the software industry.
Olli Salli
I use a design similar to the one in the Enginuity articles. As has been said already, modular code makes the code cleaner and often easier to isolate bugs.

I use the kernel design, though my implementation is different from that in the articles. I found that I didn't need alot of the other things. For smart pointers and serialization I use Boost, I have a different error logging system, I didn't need the triggers and interpolators or the profiler.

Quote:Original post by Spoonbender
I know there are some pretty glaring bugs and/or design flaws in the Enginuity articles. The author has said a few times he wished he'd never written the articles... Keep that in mind when you read the articles. There are plenty of clever ideas in there, but there are some silly ones too.


That's interesting. I'm curious what bugs or design flaws you were thinking about?
Let's not get teperamental.Personally I love Java simplification over the superdense nature of C++ syntactic sugar.
I don't like operator redefinition,templates,friends, and so on(at least not now).I believe Java has captured the essence of real oop.The problem is it is interpreted + it runs other threads in background-a managed environment-so it tends to be generally slow with a variable speed, depending on background task with no real way to overcome this.
Other than that, it is a wise simplification of C++, more in tone with the magnificent C(C++ is the rococo version of C, while Java is the modernist simplification).
I don't know C#, but I like Java best over C++(though I first learnt C about 12 years ago and Java only about 8).
I don't like &#106avascript (though it may be better suited for its job than a Java implementation) and I really dislike C++(though I make most of my home projects in C++(for speed).
Quote:Original post by James Trotter
That's interesting. I'm curious what bugs or design flaws you were thinking about?


Can't remember exactly. He ranted on about it on the irc channel a while back.
One thing was that the reference counting stuff screwed up when objects were allocated on the stack. And he didn't seem particularly fond of the whole multiple inheritance business either... But as I said, I can't remember, I was just listening to a random rant on IRC a while back. [wink]

Contact the author if you're curious
My design is similar to the stuff in the Enginuity articles. Not quite the same, but similar. The articles have got a lot of clever ideas in them, and they're definitely insightful reading for anyone not experienced with OOP, but they aren't the be all and end all of engine design either.

I think it would be a nice starting point to begin on your own engine.
I'm using this kernel based approach based on Enginuity, for almost 1,5 year in all my games, and I must say that I'm pretty satisfied with it. Almost all of my engine's subsystems are tasks, also, every game/program/test introduces its own task. I didn't got any major problems with it, most were only little irritating things such as the need to create new files for class which inherits from ITask, implement methods, then create object of that class on heap, check for errors, deallocate it etc.

The one and only big problem I can remember now is that I once stumbled upon one complicated case (I won't describe it in full detail) where custom mouse cursor (some alphablended texture) had to be rendered on top of all other things, but that couldn't be guaranteed by Z-buffer, nor renderer, nor input substem, and so I had to hack it around by creating a little task responsible only for rendering mouse cursor at right time.

However, as I stated above, that was one and only major problem I had with this design. IMHO it's very useful to have sth similar built into engine, since then you're able to add new features into the engine without breaking any dependencies. Ie, it would be very easy for me to add a profiler task for my game, which would be responsible for rendering realtime graphs of how much memory is used, which parts of the engine take how many % of CPU time - it's just a matter of adding one new task... yup, adding one new task to engine which is now 1,5 year old and while I was designing it, I never thought about adding things such as profiler :-) As you see, it's quite modular.

I copied Superpig's code and changed it a bit in some places, and few things have been added (kinda organical growth). Here's my code:

ITask.h:
//! Base class which every other task must implement. It is internally used by Kernel. //! Should be manually created on heap using operator new() and destroyed at end using delete().  class ITask  {   public:        ITask(const char * _name) : canKill(false), autoDelete(false), priority(0), name(_name) { }        virtual ~ITask() { }     // -------------------------------------------------        //@{        /*         Following three functions must be implemented in every task (although, they can be empty).        */        //! Called when task is initially added to Kernel list.    virtual bool Start() = 0;        //! Called on every Kernel list update.    virtual void Update() = 0;        //! Called when task is removed from list.    virtual void Stop() = 0;        //@}   // -------------------------------------------------   //@{   //! Called when task was suspended (which means, it stopped getting Update() calls) and now it's going back to work.   virtual void OnResume() { }           //! Called when task is going to be suspended for a while.   virtual void OnSuspend() { }      //@}   // -------------------------------------------------   //@{     //! If you set it to true, this task will be removed from Kernel list.   void SetCanKill(bool _can) { canKill = _can; }      //! Returns whether this task can be removed from Kernel queue.   bool GetCanKill() const { return canKill; }         //@}// -------------------------------------------------   //@{   //! Used for sorting tasks in the Kernel list.   //! Tasks which have smaller priority, will be updated first.   void SetPriority(ulint _p) { priority = _p; }      //! Returns priority of this task.   ulint GetPriority() const { return priority; }      //@}   // -------------------------------------------------      //! Returns the name of this task.   const char * GetName() const { return name.c_str(); }      bool GetAutoDelete() const { return autoDelete; }// -------------------------------------------------  protected:      bool canKill, autoDelete;    ulint priority;        std :: string name;  };


Kernel.h:
#include <list>#include "task.h"#include "../../Others/Misc/singleton.h"// -------------------------------------------------//! Use this macro to get access to Kernel singleton.#define getKernel SC::Kernel::get()// -------------------------------------------------namespace SC {// -------------------------------------------------   //! It's the beating heart of the engine, responsible for "pumping" various tasks.//! There's distinction between tasks that are running, and tasks that are paused.//! Some functions work on both of them, some only on one kind.class Kernel : public Singleton<Kernel> {  public:     Kernel() {}   virtual ~Kernel() {}   // -------------------------------------------------     //! Starts main loop of application.   void Execute();      //! Removes all tasks and closes application.   void CloseApp();      void LogAllTasksInfo() const;      //! Removes all running tasks.   void KillAllTasks();      //! Returns task by its name, that was specified at creating it.   //! Returned task may be on list of running or paused tasks.   //! \return Pointer to task or 0 when task not found.   ITask * GetTaskByName(const char * _name) const;   // -------------------------------------------------              bool AddTask(ITask * _task);      void RemoveTask(ITask * _task);      //! Removes dead tasks from running tasks.   usint RemoveDeadTasks();   // -------------------------------------------------         void SuspendTask(ITask * _task);      void ResumeTask(ITask * _task);   // -------------------------------------------------     private:   typedef std :: list < ITask *> TaskList;   typedef TaskList :: iterator TaskListItor;   typedef TaskList :: const_iterator TaskListConstItor;      TaskList runningTasks;   TaskList pausedTasks;      };// -------------------------------------------------  }


Kernel.cpp:
#include <algorithm>//#include <string>#include "SDL.h"#include "kernel.h"#include "../Input/inputUtility.h"#include "../../Others/Misc/misc.h"#include "../../Others/Logger/logger.h"#include "../../Layer1/MemoryMgr/mmgr.h"// ------------------------------------------------- start Execute void SC :: Kernel :: Execute() {    ITask * tmp;  TaskListConstItor itor;  TaskListItor thisIt, itorTmp; // used for erasing  SDL_Event event;    log2("Kernel", "Starting application part.")  // ------------------------------------------------- Deal with SDL events    while (!runningTasks.empty())   {     if ( SDL_PollEvent ( &event ) )	  {	   if ((event.type == SDL_KEYUP) || (event.type == SDL_KEYDOWN ) || (event.type == SDL_MOUSEBUTTONDOWN) )	    {         // theese three must be catched by input subsystem         SDL_PushEvent(&event);	    }             else if ( event.type == SDL_QUIT )  	    {	     	     log2("Kernel", "Came external quit message.")	     CloseApp();	     	     	     	    }          else if ( event.type == SDL_VIDEORESIZE )	    {	     log2("Kernel", "Came external window resize message. Ignored.")	    }	           	  }      // -------------------------------------------------   // you can close immediately application by pressing F12 in debug builds   #ifdef __SC_DEBUG__   	 if (getInput.GetKeyboardKeyNowPressed(SDLK_F12))	  {       log2("Kernel", "F12 was pressed in debug build - closing program.")       CloseApp();      }         #endif	  // -------------------------------------------------	      {     // loop and update all active tasks           	 for (itor = runningTasks.begin(); itor != runningTasks.end(); ) 	   {        tmp = (*itor); // ???????? what's this construction needed for?		++itor;		if (!tmp->GetCanKill()) tmp->Update();	   }	   // -------------------------------------------------	   	   //loop again to remove dead tasks	   for (itorTmp = runningTasks.begin(); itorTmp != runningTasks.end(); )	   {	    tmp = (*itorTmp);	    thisIt = itorTmp;	    ++itorTmp;	    	    if (tmp->GetCanKill())	     {	        	      tmp->Stop();		      	      runningTasks.erase(thisIt);          	      	      if (tmp->GetAutoDelete())		   delete tmp;	      tmp = 0;		 }	   }		 // -------------------------------------------------	   	 }   } } // ------------------------------------------------- end Execute // ------------------------------------------------- start CloseAppvoid SC :: Kernel :: CloseApp() {  KillAllTasks();  // + do anything else } // ------------------------------------------------- end CloseApp // ------------------------------------------------- start AddTask   bool SC :: Kernel :: AddTask(SC :: ITask * _task) {    if (!_task->Start())   {    logError2("Kernel", "This task couldn't start right now:")    logError2( IntToString(_task->GetPriority()).c_str(), _task->GetName());    return false;   }     TaskListItor it;    ITask* tmp;    for ( it = runningTasks.begin(); it != runningTasks.end(); ++it)   {    tmp =  (*it);    if (tmp->GetPriority() > _task->GetPriority()) break;      }    runningTasks.insert(it,_task);    std :: string temp = "Added task: ";  temp += _task->GetName();  temp +=  ", with priority: ";  temp += IntToString(_task->GetPriority());    log2("Kernel", temp.c_str());  return true; } // -------------------------------------------------  end AddTask // ------------------------------------------------- start RemoveTask void SC :: Kernel :: RemoveTask(SC :: ITask* _task) {      if ( std :: find(runningTasks.begin(), runningTasks.end(), _task) != runningTasks.end())   {    _task->SetCanKill(true);    return;    }   logError2("Kernel.RemoveTask", "Specifed task not found.") }// -------------------------------------------------  end RemoveTask// ------------------------------------------------- start RemoveDeadTasksSC :: usint SC :: Kernel :: RemoveDeadTasks() {  ITask * tmp;  TaskListItor thisIt, itorTmp;  usint count = 0;  for ( itorTmp = runningTasks.begin(); itorTmp != runningTasks.end(); )   {    tmp = (*itorTmp);    thisIt = itorTmp;    ++itorTmp;    if (tmp->GetCanKill())     {      tmp->Stop();      runningTasks.erase(thisIt);	  tmp = 0;	  ++count;     }   }  return count; }// ------------------------------------------------- end RemoveDeadTasks// ------------------------------------------------- start SuspendTaskvoid SC :: Kernel :: SuspendTask(SC :: ITask* _task) {  TaskListItor it = std :: find(runningTasks.begin(),runningTasks.end(),_task);    if (it != runningTasks.end())   {    _task->OnSuspend();    runningTasks.erase(it);    pausedTasks.push_back(_task);    return;   }     logError2("Kernel.SuspendTask", "Specifed task not found.")    }  // ------------------------------------------------- end SuspendTask// ------------------------------------------------- start ResumeTask void SC :: Kernel :: ResumeTask(SC :: ITask* _task) {   TaskListItor it =  std :: find(pausedTasks.begin(),pausedTasks.end(),_task);    if (it != pausedTasks.end())   {    _task->OnResume();        pausedTasks.erase(it); // faster than remove     	ITask* tmp;    	for ( it = runningTasks.begin(); it != runningTasks.end(); ++it)	 {	  tmp =  (*it);	  if (tmp->GetPriority() > _task->GetPriority()) break;   	 }    	runningTasks.insert(it,_task);  	return;    }   logError2("Kernel.ResumeTask", "Specifed task not found.")    } // ------------------------------------------------- end ResumeTask // ------------------------------------------------- start GetTaskByNameSC :: ITask * SC :: Kernel :: GetTaskByName(const char * _name) const {  TaskListConstItor itor;  for ( itor = runningTasks.begin(); itor != runningTasks.end(); ++itor)   {    if (!strcmp((*itor)->GetName(), _name) )     return (*itor);   }     for ( itor = pausedTasks.begin(); itor != pausedTasks.end(); ++itor)   {    if (!strcmp((*itor)->GetName(), _name) )     return (*itor);   }     return 0; }// ------------------------------------------------- end GetTaskByName// ------------------------------------------------- start KillAllTasks void SC :: Kernel :: KillAllTasks() {  TaskListItor itor;    for ( itor = runningTasks.begin(); itor != runningTasks.end(); ++itor)   {    (*itor)->SetCanKill(true);   }    log2("Kernel", "Removed all tasks from the list.")    }   // ------------------------------------------------- end KillAllTasks// ------------------------------------------------- start LogAllTasksInfovoid SC :: Kernel :: LogAllTasksInfo() const {  TaskListConstItor itor;    log2("Kernel", "Current list of tasks");    beginG("RUNNING")  for ( itor = runningTasks.begin(); itor != runningTasks.end(); ++itor)   {    log2( IntToString((*itor)->GetPriority()).c_str(), (*itor)->GetName());   }     endG;// -------------------------------------------------    beginG("PAUSED")  for ( itor = pausedTasks.begin(); itor != pausedTasks.end(); ++itor)   {    log2( IntToString((*itor)->GetPriority()).c_str(), (*itor)->GetName());   }     endG; }// ------------------------------------------------- end LogAllTasksInfo
Quote:Original post by Koshmaar
Almost all of my engine's subsystems are tasks, also, every game/program/test introduces its own task.

...since then you're able to add new features into the engine without breaking any dependencies. Ie, it would be very easy for me to add a profiler task for my game, which would be responsible for rendering realtime graphs of how much memory is used, which parts of the engine take how many % of CPU time - it's just a matter of adding one new task... yup


That's the major benefit I saw from using this aproach.

What I see as problematic is the interrelation between subsystems. Say, between the world objects, the physics, the renderer etc.

I'm not sure where to place the access point for the subsystems to be able to communicate with each other in this kind of task-fashioned design. I also feel this design pushes me to make heavy use of the singleton pattern, and I feel really tempted to make each subsystem a singleton, which would make the whole tasked stuff more a waste of time inside the loop than a gain.
[size="2"]I like the Walrus best.

This topic is closed to new replies.

Advertisement