Ticking

Optimizing Ticking for Large Object Counts

How can we optimize ticking for a large number of objects without sacrificing performance?

Abstract art representing computer programming

Optimizing ticking for a large number of objects is crucial for maintaining good performance in complex games. Here are several strategies to achieve this.

Object Pooling

Instead of creating and destroying objects frequently, use an object pool to reuse inactive objects.

#include <queue>
#include <vector>

template <typename T>
class ObjectPool {
public:
  ObjectPool(size_t initialSize) {
    for (size_t i = 0; i < initialSize; ++i) {
      objects.emplace_back(
        std::make_unique<T>());
      available.push(&(*objects.back()));
    }
  }

  T* Get() {
    if (available.empty()) {
      objects.emplace_back(
        std::make_unique<T>());
      available.push(&(*objects.back()));
    }
    T* obj = available.front();
    available.pop();
    return obj;
  }

  void Return(T* obj) { available.push(obj); }

private:
  std::vector<std::unique_ptr<T>> objects;
  std::queue<T*> available;
};

class Bullet : public GameObject {
  // Bullet implementation
};

class BulletManager {
public:
  BulletManager() : pool(1000) {}

  void SpawnBullet() {
    Bullet* bullet = pool.Get();
    activeBullets.push_back(bullet);
  }

  void TickAll() {
    for (
      auto it = activeBullets.begin();
      it != activeBullets.end();
    ) {
      if ((*it)->IsActive()) {
        (*it)->Tick();
        ++it;
      } else {
        pool.Return(*it);
        it = activeBullets.erase(it);
      }
    }
  }

private:
  ObjectPool<Bullet> pool;
  std::vector<Bullet*> activeBullets;
};

Spatial Partitioning

Use spatial partitioning techniques like quadtrees or grid systems to only update objects that are relevant to the current game state.

#include <unordered_map>

class GridCell {
public:
  void AddObject(GameObject* obj) {
    objects.push_back(obj);
  }

  void RemoveObject(GameObject* obj) {
    objects.erase(
      std::remove(objects.begin(),
                  objects.end(), obj),
      objects.end());
  }

  void TickAll() {
    for (auto* obj : objects) { obj->Tick(); }
  }

private:
  std::vector<GameObject*> objects;
};

class SpatialGrid {
public:
  void AddObject(
    GameObject* obj, int x, int y
  ) {
    int cellX = x / cellSize;
    int cellY = y / cellSize;
    grid[{cellX, cellY}].AddObject(obj);
  }

  void TickAll() {
    for (auto& [pos, cell] : grid) {
      cell.TickAll();
    }
  }

private:
  std::unordered_map<
    std::pair<int, int>, GridCell> grid;
  int cellSize{100}; // Size of each grid cell
};

Multithreading

Utilize multiple threads to update objects in parallel.

#include <mutex>
#include <thread>

class World {
public:
  void TickAll() {
    const size_t numThreads =
      std::thread::hardware_concurrency();
    std::vector<std::thread> threads;

    for (size_t i = 0; i < numThreads; ++i) {
      threads.emplace_back(
        [this, i, numThreads](){
          for (size_t j = i; j < objects.size();
               j += numThreads) {
            objects[j]->Tick();
          }
        });
    }

    for (auto& thread : threads) {
      thread.join();
    }
  }

private:
  std::vector<std::unique_ptr<GameObject>>
  objects;
  std::mutex objectsMutex;
};

Data-Oriented Design

Organize data for cache-friendly access patterns.

class ComponentSystem {
public:
  void AddObject(int x, int y, float velocity) {
    positions.emplace_back(x, y);
    velocities.push_back(velocity);
  }

  void TickAll() {
    for (
      size_t i = 0;
      i < positions.size();
      ++i
    ) {
      positions[i].x += velocities[i];
      positions[i].y += velocities[i];
    }
  }

private:
  struct Position {
    int x, y;
  };

  std::vector<Position> positions;
  std::vector<float> velocities;
};

Lazy Evaluation

Only update objects when their state is actually needed.

class LazyGameObject {
public:
  void SetNeedsUpdate() { needsUpdate = true; }

  void Tick() {
    if (needsUpdate) {
      DoUpdate();
      needsUpdate = false;
    }
  }

private:
  bool needsUpdate{false};

  void DoUpdate() {
    // Perform actual update logic
  }
};

Update Frequency Management

Allow different update frequencies for different types of objects.

class GameObject {
public:
  GameObject(int updateFrequency) :
    updateFrequency(updateFrequency) {}

  void Tick(int currentFrame) {
    if (currentFrame % updateFrequency == 0) {
      DoTick();
    }
  }

private:
  int updateFrequency;

  virtual void DoTick() = 0;
};

By implementing these optimization techniques, you can significantly improve the performance of your game when dealing with a large number of objects.

Remember to profile your game to identify bottlenecks and apply these optimizations where they'll have the most impact.

Answers to questions are automatically generated and may not have been reviewed.

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 62 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved