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