This project shows off a 2D multithreaded game engine, designed from scratch and implemented using C++ and SDL.
About the project
The whole project was completed from start of research to completed implementation in seven weeks time. The first three weeks consisted of pure research on multithreaded game engines, while the last 4 weeks were more about testing certain systems and creating the actual game engine from scratch.
The engine features an entity component system, controller support, keyboard and mouse support, asynchronous loading and multithreading. The core of the multithreading lies in the Task manager, the main thread and the worker threads.
A more in depth explanation on the project can be read in my paper .
Code Snippets
Task Manager
This code snippet shows how the task manager works. Important to note is the component map used in nearly all functions in the task manager. This is a map that holds a vector of components per component type. This map is used to create all tasks, and to execute these tasks. With this design, the task manager becomes a bottleneck when many threads want to access it at the same time, this will be changed in future version but could not be done in time for the deadline. The paper lays out several methods that could be used to get rid of this bottleneck, but they are untested.
1#include "pch.h"
2#include "TaskManager.h"
3#include "Components/RenderComponent.h"
4#include "Engine/Engine.h"
5#include "Game/Game.h"
6
7//Function called by a scene when it gets loaded as the active scene, it initializes all keys, counters and sets the reference to the component map
8//This map is used to create and execute all tasks
9void anx::TaskManager::SetComponentMap(std::map<size_t, std::vector<BaseComponent*>>& componentMap)
10{
11 m_NrThreads = ThreadPool::GetInstance().GetNrThreads();
12
13 m_pComponentMap = &componentMap;
14
15 m_Keys.clear();
16 m_Counters.clear();
17 m_LockedKeys.clear();
18 for(auto component: *m_pComponentMap)
19 {
20 if(!component.second.front()->IsLockedToMainThread())
21 {
22 m_Keys.push_back(component.first);
23 m_Counters[component.first].store(0);
24 }
25 else
26 {
27 m_LockedKeys.push_back(component.first);
28 }
29
30 }
31}
32
33bool anx::TaskManager::GetJobRequest(Task& task, bool mainThread)
34{
35 std::unique_lock<std::mutex>lock(m_TaskMutex);
36 //If the engine is notified to quit, make the worker thread exit the loop
37 if (m_Quit) return false;
38
39 if (m_Tasks[int(m_CurrentStage)].try_pop(task))
40 return true;
41
42 //If the main thread wanted to get a job, but all jobs were completed, make sure to notify a worker thread to create new tasks
43 if (mainThread)
44 {
45 m_TaskCondition.notify_one();
46 return false;
47 }
48
49 //When there are no tasks available, wait for more to become available if not al tasks have finished executing
50 if (!AllCountersZero())
51 {
52 m_TaskCondition.wait(lock);
53 return false;
54 }
55
56 m_NextStage = Stage((int(m_CurrentStage) + 1) % 5);
57 m_CurrentStage = Stage::SIZE;
58
59 //Reset the state of the task queue
60 ResetCounters();
61 ResetFlags();
62
63 if (m_NextStage == Stage::SWAPPF)
64 {
65 //Wait for the main thread to complete drawing before swapping the buffers
66 Engine::GetInstance().SyncWithMain();
67 }
68 if (m_NextStage == Stage::UPDATE)
69 {
70 //Wait for the main thread to complete handling input and cleaning resources before starting the next frame
71 Engine::GetInstance().SyncWithMain();
72 lock.unlock();
73 m_CurrentStage = m_NextStage;
74 //Create frame tasks for the new frame
75 CreateFrameTasks();
76 }
77
78
79 //Notify all waiting threads new tasks have been added, once possible sync with main happened
80 if(m_CurrentStage != Stage::UPDATE)
81 {
82 m_CurrentStage = m_NextStage;
83 }
84
85 m_TaskCondition.notify_all();
86 return false;
87}
88
89//Function used by the main thread to handle tasks that require SDL function calls
90bool anx::TaskManager::GetLockedJobRequest(Task & task)
91{
92 if (m_Quit) return false;
93
94 if (m_LockedTasks.try_pop(task))
95 return true;
96
97 m_TaskCondition.notify_one();
98 return false;
99}
100
101//Used by the input manager to add a command job to the job queue
102void anx::TaskManager::AddCommandJob(Command * pToAdd)
103{
104 if (!pToAdd) return;
105 //Create a new task with the command bound
106 Task newTask;
107 newTask.stage = Stage::COMMAND;
108 newTask.pCommand = pToAdd;
109 //Add the command to the task queue
110 m_Tasks[int(Stage::UPDATE)].push(newTask);
111 //Notify the condition variable that there is a new task available
112 m_TaskCondition.notify_one();
113}
114
115//Used by the scene to add a request to load a new scene
116void anx::TaskManager::AddLoadingJob(Scene * pToLoad)
117{
118 if (!pToLoad) return;
119 //Create a new task with the command bound
120 Task newTask;
121 newTask.stage = Stage::LOADING;
122 newTask.pScene = pToLoad;
123 //Add the command to the task queue
124 m_Tasks[int(Stage::UPDATE)].push(newTask);
125 //Notify the condition variable that there is a new task available
126 m_TaskCondition.notify_one();
127}
128
129//Can be called from anywhere, usually the constructor of a component
130//makes sure all components of type base get updated before tasks of dependantOn type get executed
131void anx::TaskManager::SetDependecny(size_t base, size_t dependantOn)
132{
133 m_Dependencies[base].push_back(dependantOn);
134}
135
136anx::Stage anx::TaskManager::GetCurrentStage() const
137{
138 return m_CurrentStage;
139}
140
141
142//This function has to get called after a certain task group has completed
143void anx::TaskManager::CheckDependencies(size_t componentHash)
144{
145 for (size_t i = 0; i < m_Keys.size(); ++i)
146 {
147 size_t key = m_Keys.at(i);
148 //If this component type has already been marked updated, ignore it
149 if (m_ComponentUpdated[key])
150 continue;
151 auto result = std::find(m_Dependencies[key].begin(), m_Dependencies[key].end(), componentHash);
152 //If this key was depending on the completed taskset, try to create the task set for the depending key
153 if(result != m_Dependencies.at(key).end())
154 {
155 CreateTasks(key, m_CurrentStage);
156 }
157 }
158}
159
160//Clears the counters for all component types that don't have to be updated this frame
161//Also marking these components complete and checks for dependant components
162void anx::TaskManager::ClearEmptyCounters()
163{
164 for (size_t i = 0; i < m_Keys.size(); ++i)
165 {
166 size_t componentHash = m_Keys[i];
167 if (!m_pComponentMap->at(componentHash).at(0)->ShouldExecuteStage(m_NextStage))
168 {
169 m_Counters[componentHash] = 0;
170 m_ComponentUpdated[componentHash] = true;
171 CheckDependencies(componentHash);
172 }
173 }
174 for (size_t i = 0; i < m_LockedKeys.size(); ++i)
175 {
176 size_t componentHash = m_LockedKeys[i];
177 if (!m_pComponentMap->at(componentHash).at(0)->ShouldExecuteStage(m_NextStage))
178 {
179 m_Counters[componentHash] = 0;
180 m_ComponentUpdated[componentHash] = true;
181 CheckDependencies(componentHash);
182 }
183 }
184}
185
186//Sets all counters to the number of the corresponding components in the current scene
187void anx::TaskManager::ResetCounters()
188{
189 if(m_NextStage == Stage::PHYSICSINIT)
190 {
191 size_t nrColliders = m_pComponentMap->at(m_ColliderHash).size();
192 m_Counters[m_ColliderHash] = nrColliders;
193 }
194 else if (m_NextStage == Stage::PHYSICSMAIN)
195 {
196 CreateBaseTasks(Stage::PHYSICSMAIN);
197 auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
198 int size = int(physScene->GetTree()->GetNotEmptyNodes().size());
199 m_Counters[m_ColliderHash] = size;
200 }
201 else
202 {
203 for (size_t i = 0; i < m_Keys.size(); ++i)
204 {
205 size_t index = m_Keys.at(i);
206 m_Counters.at(index) = int(m_pComponentMap->at(index).size());
207 }
208 ClearEmptyCounters();
209 }
210}
211
212//Creates the base tasks for all component types in the current scene
213void anx::TaskManager::CreateBaseTasks(Stage stage)
214{
215 switch (stage)
216 {
217 case Stage::UPDATE:
218 case Stage::LATEUPDATE:
219 case Stage::SWAPPF:
220 //Try to create tasks for all component group types
221 for (size_t i = 0; i < m_Keys.size(); ++i)
222 {
223 CreateTasks(m_Keys.at(i), stage);
224 }
225
226 //Create locked tasks for all locked task groups
227 for (size_t i = 0; i < m_LockedKeys.size(); ++i)
228 {
229 CreateLockedTasks(m_LockedKeys.at(i), stage);
230 }
231 break;
232 case Stage::PHYSICSINIT:
233 {
234 auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
235 physScene->StartUpdate();
236
237 size_t nrColliders = m_pComponentMap->at(m_ColliderHash).size();
238 size_t collidersPerTask = std::max(1, int(nrColliders / m_NrThreads));
239
240 size_t collidersHandled = 0;
241
242 while(collidersHandled < nrColliders)
243 {
244 Task newTask;
245 newTask.key = m_ColliderHash;
246 newTask.range = std::make_pair(collidersHandled, std::min(collidersHandled + collidersPerTask, nrColliders));
247 collidersHandled += collidersPerTask;
248 newTask.stage = stage;
249 m_Tasks[int(stage)].push(newTask);
250 }
251 break;
252 }
253 case Stage::PHYSICSMAIN:
254 {
255 auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
256
257 int size = int(physScene->GetTree()->GetNotEmptyNodes().size());
258
259 int nrNodesPerTask = std::max(1,int(size / m_NrThreads));
260 int nrNodesHandled = 0;
261
262 while (nrNodesHandled < size)
263 {
264 Task newTask;
265 newTask.key = m_ColliderHash;
266 newTask.range = std::make_pair(nrNodesHandled, std::min(nrNodesHandled + nrNodesPerTask, size));
267 nrNodesHandled += nrNodesPerTask;
268 newTask.stage = stage;
269 m_Tasks[int(stage)].push(newTask);
270 }
271 }
272 break;
273 default: ;
274 }
275}
276
277//Reduces the component counter for the given component type by the given amount
278//If the counter becomes 0 as a result, it flages the component as completed and checks for dependant component types
279void anx::TaskManager::ReduceCounter(size_t componentHash, int toReduce)
280{
281 m_Counters.at(componentHash) -= toReduce;
282 if (m_Counters.at(componentHash) == 0)
283 {
284 m_ComponentUpdated[componentHash] = true;
285 CheckDependencies(componentHash);
286 }
287}
288
289std::vector<anx::BaseComponent*>& anx::TaskManager::GetComponents(size_t componentHash)
290{
291 return m_pComponentMap->at(componentHash);
292}
293
294void anx::TaskManager::Quit()
295{
296 m_Quit = true;
297 m_TaskCondition.notify_all();
298}
299
300anx::TaskManager::TaskManager()
301 :m_CurrentStage(Stage::SWAPPF), m_pComponentMap(nullptr), m_RenderHash(ComponentID<RenderComponent>::GetID())
302 ,m_ColliderHash(ComponentID<ColliderComponent>::GetID())
303{
304}
305
306//Function that created the actual tasks per component type and stage
307void anx::TaskManager::CreateTasks(size_t componentHash, Stage stage)
308{
309 int size = int(m_pComponentMap->at(componentHash).size());
310
311 if (!m_pComponentMap->at(componentHash).at(0)->ShouldExecuteStage(stage))
312 {
313 return;
314 }
315
316 std::vector<size_t>& dependencies = m_Dependencies[componentHash];
317 for (int i = 0; i < int(dependencies.size()); ++i)
318 {
319 //If one of the counter of the dependant task lists is not yet 0, don't create the tasks.
320 if (!m_Counters.at(dependencies.at(i)) == 0)
321 return;
322 }
323
324 int nrObjectsPerTask = std::max(int(size / m_NrThreads), 10);
325 int nrObjectsHandled = 0;
326
327 while (nrObjectsHandled < size)
328 {
329 Task newTask;
330 newTask.key = componentHash;
331 newTask.range = std::make_pair(nrObjectsHandled, std::min(nrObjectsHandled + nrObjectsPerTask, size));
332 nrObjectsHandled += nrObjectsPerTask;
333 newTask.stage = stage;
334 m_Tasks[int(stage)].push(newTask);
335 }
336
337 m_TaskCondition.notify_one();
338}
339
340//Same as previous function, but for the tasks locked to the main thread
341void anx::TaskManager::CreateLockedTasks(size_t componentHash, Stage stage)
342{
343 if(!m_pComponentMap->at(componentHash).at(0)->ShouldExecuteStage(stage))
344 {
345 return;
346 }
347
348 int size = int(m_pComponentMap->at(componentHash).size());
349
350 std::vector<size_t>& dependencies = m_Dependencies[componentHash];
351 for (int i = 0; i < int(dependencies.size()); ++i)
352 {
353 //If one of the counter of the dependant task lists is not yet 0, don't create the tasks.
354 if (!m_Counters.at(dependencies.at(i)) == 0)
355 return;
356 }
357 //If the number of components exceed the maximum number of components per task, create sets with the max size
358 if (size > m_MaxTaskSize)
359 {
360 for (int i = 0; i < size; i += m_MaxTaskSize)
361 {
362 Task newTask;
363 newTask.key = componentHash;
364 newTask.range = std::make_pair(i, std::min(i + m_MaxTaskSize, size));
365 newTask.stage = stage;
366 m_LockedTasks.push(newTask);
367 }
368 }
369 else
370 {
371 Task newTask;
372 newTask.key = componentHash;
373 newTask.range = std::make_pair(0, size);
374 newTask.stage = stage;
375 m_LockedTasks.push(newTask);
376 }
377 m_TaskCondition.notify_one();
378}
379
380//Base Function that starts the process of creating al frame tasks
381void anx::TaskManager::CreateFrameTasks()
382{
383 for (int i = 0; i < int(Stage::SIZE); ++i)
384 {
385 if (i == int(Stage::PHYSICSMAIN)) ++i;
386 CreateBaseTasks(Stage(i));
387 }
388}
389
390//Function that checks if all components have finished updating
391bool anx::TaskManager::AllCountersZero()
392{
393 //std::unique_lock<std::mutex>lock(m_CounterMutex);
394 for (size_t i = 0; i < m_Keys.size(); ++i)
395 {
396 size_t key = m_Keys.at(i);
397 if(m_Counters[key] > 0)
398 return false;
399 }
400 return true;
401}
402
403//Resets the completed flag per component type
404void anx::TaskManager::ResetFlags()
405{
406 for (size_t i = 0; i < m_Keys.size(); ++i)
407 {
408 m_ComponentUpdated[m_Keys.at(i)] = false;
409 }
410}
Thread pool
The next code snippet shows the implementation of the thread pool. This pool holds a number of worker threads (depending on the number of physical cores in the system) that constantly pull tasks from the task manager and execute them using the current scene’s component map.
1#include "pch.h"
2#include "ThreadPool.h"
3#include "Engine/Engine.h"
4#include "TaskManager.h"
5#include "Game/Game.h"
6#include "Commands/Command.h"
7
8//Static variables used by the engine to signal a quit event
9std::mutex anx::ThreadPool::QuitMutex{};
10bool anx::ThreadPool::m_Quit = false;
11std::mutex anx::ThreadPool::m_GroupMutex{};
12std::condition_variable anx::ThreadPool::m_GroupCV{};
13
14//The destructor makes all threads exit and joins them before returning.
15anx::ThreadPool::~ThreadPool()
16{
17 m_Quit = true;
18 TaskManager::GetInstance().Quit();
19 for (size_t i = 0; i < m_Threads.size(); ++i)
20 {
21 TaskManager::GetInstance().Quit();
22 Engine::GetInstance().Quit();
23 m_Threads.at(i).join();
24 }
25}
26
27//Initialization function that created worker threads depending on the number of physical cores in the system
28void anx::ThreadPool::Init()
29{
30 m_NrThreads = std::thread::hardware_concurrency() - 1;
31
32 for (int i = 0; i < m_NrThreads; ++i)
33 {
34 m_Threads.emplace_back(std::thread{ &ThreadPool::InfiniteLoop, this });
35 }
36}
37
38anx::ThreadPool::ThreadPool()
39 :m_NrThreads(0)
40{
41}
42
43//Inifite loop function that gets executed by every worker thread until the game notifies the engine quit
44void anx::ThreadPool::InfiniteLoop()
45{
46 anx::TaskManager& manager = anx::TaskManager::GetInstance();
47 anx::Engine& engine = anx::Engine::GetInstance();
48 Task currentTask;
49
50 while(!engine.GetQuit())
51 {
52 //Get a job request from the task manager
53 bool hasTask = manager.GetJobRequest(currentTask);
54 //If we got a task, execute it depending on it's stage and component type
55 if(hasTask)
56 {
57 int size = currentTask.range.second - currentTask.range.first;
58 switch (currentTask.stage)
59 {
60 case Stage::UPDATE:
61 {
62 auto& components = manager.GetComponents(currentTask.key);
63 for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
64 {
65 components.at(i)->Update();
66 }
67 manager.ReduceCounter(currentTask.key, size);
68 }
69 break;
70 //Physics init stage adds all collider components to the scene's quad tree
71 case Stage::PHYSICSINIT:
72 {
73 auto& components = manager.GetComponents(currentTask.key);
74 auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
75 physScene->Insert(currentTask.range.first, currentTask.range.second);
76 manager.ReduceCounter(currentTask.key, size);
77 }
78 break;
79 //The main physics stage is going to process each node and check for collisions between collider components.
80 //This information is passed to the collider components by a adding a reference to either the overlapping or blocking entity list of the collider component
81 case Stage::PHYSICSMAIN:
82 {
83 auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
84 auto& nodes = physScene->GetTree()->GetNotEmptyNodes();
85 for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
86 {
87 physScene->ProcessNode(nodes[i]);
88 }
89 manager.ReduceCounter(currentTask.key, size);
90 }
91
92 break;
93 case Stage::LATEUPDATE:
94 {
95 auto& components = manager.GetComponents(currentTask.key);
96 for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
97 {
98 components.at(i)->LateUpdate();
99 }
100 manager.ReduceCounter(currentTask.key, size);
101 }
102
103 break;
104 case Stage::SWAPPF:
105 {
106 auto& components = manager.GetComponents(currentTask.key);
107 for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
108 {
109 components.at(i)->SwapPF();
110 }
111 manager.ReduceCounter(currentTask.key, size);
112 }
113 break;
114 //Command and Loading tasks don't use the component map of the current scene
115 case Stage::COMMAND:
116 currentTask.pCommand->Execute();
117 break;
118 case Stage::LOADING:
119 {
120 auto game = Engine::GetInstance().GetCurrentGame();
121 game->SetLoadingScreen();
122 while (!game->IsLoadingScreen()) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
123 game->Load();
124 }
125 break;
126 default: ;
127 }
128
129 }
130 }
131}
Quad tree
The following code snippet shows an implementation of a quad tree, that can be used in a multithreaded environment. A tradeoff has been made to accommodate multiple threads inserting new collider components without blocking by having the tree always be at max size. This means we don’t have to lock at runtime to split a node but gives us a performance hit if the objects with a collider are not distributed along the scene well. To compensate for this, each scene can choose how deep the tree goes, depending on how spread out the objects will be. Even though this seems like a big deal, multithreading should only be used in scenarios where we can split up the work anyways.
1#include "pch.h"
2#include "QuadTree.h"
3
4//Static vector holding all nodes in the current tree for easy access by the worker threads
5Concurrency::concurrent_vector<anx::QuadTree*> anx::QuadTree::pNodes{};
6
7anx::QuadTree::QuadTree()
8 :m_Level(-1), m_pNodes{nullptr, nullptr, nullptr, nullptr}, m_pDynamicObjects(Concurrency::concurrent_vector<ColliderComponent*>{}),
9 m_pStaticObjects(Concurrency::concurrent_vector<ColliderComponent*>{}), m_MaxLevel(5)
10{
11}
12
13anx::QuadTree::~QuadTree()
14{
15}
16
17//Function that builds the entire tree, give the level bounds and the number of levels in the tree
18anx::QuadTree * anx::QuadTree::BuildTree(const Rect & bounds, int levels)
19{
20 pNodes.reserve(500);
21
22 QuadTree* pRootNode = ObjectPool<QuadTree>::GetInstance().RequestObject();
23 pRootNode->Initialize(0, bounds);
24 pRootNode->m_MaxLevel = levels;
25 std::queue<QuadTree*> pQueue;
26 pQueue.push(pRootNode);
27
28 while(!pQueue.empty())
29 {
30 pQueue.front()->Split(levels, pQueue);
31 pQueue.pop();
32 }
33
34 return pRootNode;
35}
36
37//Function makes sure all our children are set to nullptr and the current node knows the number of total levels in the tree
38void anx::QuadTree::Initialize(int level, const Rect & bounds)
39{
40 for (int i = 0; i < 4; ++i)
41 {
42 m_pNodes[i] = nullptr;
43 }
44
45 m_Level = level;
46 m_Bounds = bounds;
47}
48
49void anx::QuadTree::Clear()
50{
51 //Clear all nodes from the node list
52 m_pStaticObjects.clear();
53 m_pDynamicObjects.clear();
54 for (int i = 0; i < 4; ++i)
55 {
56 if(m_pNodes[i] != nullptr)
57 {
58 m_pNodes[i]->Clear();
59 }
60 }
61 pNodes.clear();
62 m_Size = 0;
63}
64
65//Splits up a node into four sub nodes with each taking care of a quarter of the current node's bounds
66void anx::QuadTree::Split()
67{
68 if (m_pNodes[0]) return;
69
70 float subWidth = m_Bounds.width * 0.5f;
71 float subHeight = m_Bounds.height * 0.5f;
72 const Vector2& topLeft = m_Bounds.topLeft;
73
74 m_pNodes[0] = ObjectPool<QuadTree>::GetInstance().RequestObject();
75 m_pNodes[0]->Initialize(m_Level + 1, Rect(topLeft, subWidth, subHeight));
76
77 m_pNodes[1] = ObjectPool<QuadTree>::GetInstance().RequestObject();
78 m_pNodes[1]->Initialize(m_Level + 1, Rect({ topLeft.x + subWidth, topLeft.y }, subWidth, subHeight));
79
80 m_pNodes[2] = ObjectPool<QuadTree>::GetInstance().RequestObject();
81 m_pNodes[2]->Initialize(m_Level + 1, Rect({ topLeft.x, topLeft.y + subHeight }, subWidth, subHeight));
82
83 m_pNodes[3] = ObjectPool<QuadTree>::GetInstance().RequestObject();
84 m_pNodes[3]->Initialize(m_Level + 1, Rect({ topLeft.x + subWidth, topLeft.y + subHeight }, subWidth, subHeight));
85}
86
87//Base function called when the tree is being built
88void anx::QuadTree::Split(int maxLevel, std::queue<anx::QuadTree*>& pQueue)
89{
90 if (m_Level < maxLevel)
91 {
92 Split();
93 for (int i = 0; i < 4; ++i)
94 {
95 pQueue.push(m_pNodes[i]);
96 }
97 }
98}
99
100//Returns the index of the child node the rectangle completely fits into
101//Or -1 is the rectangle overlaps more than one sub node
102int anx::QuadTree::GetIndex(const Rect& collider)
103{
104 for (int i = 0; i < 4; ++i)
105 {
106 const Rect& bounds = m_pNodes[i]->GetBounds();
107 if (collider.IsInside(bounds))
108 return i;
109 }
110 return -1;
111}
112
113//Finds the correct node to add the collider to, if the collider overlaps multiple nodes, it gets added in all of the overlapping nodes
114void anx::QuadTree::Insert(const Rect& colliderBounds, ColliderComponent * pCollider)
115{
116
117 if(m_pNodes[0] != nullptr)
118 {
119 for (int i = 0; i < 4; ++i)
120 {
121 Rect bounds = m_pNodes[i]->GetBounds();
122 if (colliderBounds.IsOverlapping(bounds))
123 m_pNodes[i]->Insert(colliderBounds, pCollider);
124 }
125 }
126 else
127 {
128 //Add the colliders in the correct queue, the tree supports both static and dynamic colliders
129 if(pCollider->GetIsStatic())
130 m_pStaticObjects.push_back(pCollider);
131 else
132 m_pDynamicObjects.push_back(pCollider);
133
134 if (++m_Size == 2)
135 pNodes.push_back(this);
136 }
137
138}
139
140const anx::Rect & anx::QuadTree::GetBounds()
141{
142 return m_Bounds;
143}
144
145const Concurrency::concurrent_vector<anx::ColliderComponent*>& anx::QuadTree::GetDynamicObjects() const
146{
147 return m_pDynamicObjects;
148}
149
150const Concurrency::concurrent_vector<anx::ColliderComponent*>& anx::QuadTree::GetStaticObjects() const
151{
152 return m_pStaticObjects;
153}
154
155Concurrency::concurrent_vector<anx::QuadTree*>& anx::QuadTree::GetNotEmptyNodes()
156{
157 return pNodes;
158}
159
160int anx::QuadTree::GetNrTotalNodes()const
161{
162 return pNodes.size();
163}
164
165int anx::QuadTree::GetNrNodes()
166{
167 return int(std::pow(4,m_MaxLevel));
168}