16 May, 2024

Sequencing Patterns

Double Buffer Pattern

Double Buffer

Intent Causes a series of sequential operations to appear instantaneous or simultaneous.

  • Computers are sequential
    • They do one task at a time
    • One instruction after another
  • In a game, many things need to work at the same time
    • Rendering
    • Physics calculations
    • Resource keeping
    • etc.

How computer graphics work?

  • A computer screen renders pixels one at a time
  • It starts from the top left corner and works line by line to the bottom right
  • Then it starts from scratch
  • It does this so fast, our eyes can’t catch the scanning process
    • We can only recognize the motion on the screen

How computer graphics work?

  • So where do the pixels come?
    • From the framebuffer
    • That is a part of the memory reserved for constructing the image to be displayed on the screen
  • All you have to do to draw on the screen is to change the bytes on the framebuffer
  • But, there is a catch

How computer graphics work?

  • We previously said that computers are sequential, but that is not completely true
  • Some peripheral devices work independent of the CPU
    • Screen reads the framebuffer
    • Keyboard receives key presses
    • Mouse receives mouse motion and clicks
  • So, the framebuffer is shared between the writer and the reader, simultaneously
    • That creates a very important problem
    • What if they are not in sync?

Screen vs Rendering

  • If we update the framebuffer faster than the screen can read it
    • The upper half of the screen belongs to frame i, and the bottom half belongs to frame i+1
  • If we update the framebuffer slower than the screen can read it
    • The upper half of the screen belongs to frame i+1, and the bottom half belongs to frame i
  • This is called tearing

Tearing

The cure

  • Double buffering solves screen tearing in a simple way

    • Have two framebuffers and swap them when writing is over!
  • As soon as the new framebuffer is ready, the screen starts reading from the new frame and releases the old framebuffer

  • Actually this is page flipping. Classical double buffering copies the new framebuffer on the old framebuffer when the changes are complete.

    • But this requires an extra chunky copy operation
    • So page flipping is better

When to use double buffer?

  • We have some state that is being modified incrementally.

  • That same state may be accessed in the middle of modification.

  • We want to prevent the code that’s accessing the state from seeing the work in progress.

  • We want to be able to read the state and we don’t want to have to wait while it’s being written.

A simple implementation

class Framebuffer
{
public:
  Framebuffer() { clear(); }

  void clear()
  {
    for (int i = 0; i < WIDTH * HEIGHT; i++)
    {
      pixels_[i] = WHITE;
    }
  }

  void draw(int x, int y)
  {
    pixels_[(WIDTH * y) + x] = BLACK;
  }

  const char* getPixels()  // Video driver needs this
  {
    return pixels_;
  }

private:
  static const int WIDTH = 160;
  static const int HEIGHT = 120;

  char pixels_[WIDTH * HEIGHT];
};

A simple implementation

  • Now imagine this happens:
class Scene
{
public:
  void draw()
  {
    buffer_.clear();

    buffer_.draw(1, 1);
    buffer_.draw(4, 1);
    // <- Video driver reads pixels here!
    buffer_.draw(1, 3);
    buffer_.draw(2, 4);
    buffer_.draw(3, 4);
    buffer_.draw(4, 3);
  }

  Framebuffer& getBuffer() { return buffer_; }

private:
  Framebuffer buffer_;
};

A simple implementation

  • The fix:
class Scene
{
public:
  Scene()
  : current_(&buffers_[0]),
    next_(&buffers_[1]) {}

  void draw()  {
    next_->clear();
    next_->draw(1, 1);
    // ...
    next_->draw(4, 3);
    swap();
  }

  Framebuffer& getBuffer() { return *current_; }

private:
  void swap()  {    // Just switch the pointers.
    Framebuffer* temp = current_;
    current_ = next_;
    next_ = temp;  
    // The next time screen asks for the pointer, it gets the new one
  }

  Framebuffer  buffers_[2];
  Framebuffer* current_;
  Framebuffer* next_;
};

Why do we need to know this?

  • You may say that modern game engines handle this
    • So why do we need to learn this?
    • Because it is not useful only for rendering
  • Double buffering can be useful when the state of an object is both being updated and used at the same
    • Physics computations
    • AI

AI

  • Consider a scene with multiple actors. They sit on a circle, next to each other. When one actor slaps the actor next to him, the slapped actor then slaps the actor next to him.
  • An actor is implemented as:
class Actor
{
public:
  Actor() : slapped_(false) {}

  virtual ~Actor() {}
  virtual void update() = 0;

  void reset()      { slapped_ = false; }
  void slap()       { slapped_ = true; }
  bool wasSlapped() { return slapped_; }

private:
  bool slapped_;
};
  • Every frame, the game is responsible for calling update() on the actor so that it has a chance to do some processing. Critically, from the user’s perspective, all actors should appear to update simultaneously.

AI

The actors need a stage where they can interact, so let’s build that:

class Stage
{
public:
  void add(Actor* actor, int index)
  {
    actors_[index] = actor;
  }

  void update()
  {
    for (int i = 0; i < NUM_ACTORS; i++)
    {
      actors_[i]->update();
      actors_[i]->reset();
    }
  }

private:
  static const int NUM_ACTORS = 3;

  Actor* actors_[NUM_ACTORS];
};

AI

To get things going, let’s define a concrete actor subclass. Our comedian here is pretty simple. He faces a single actor. Whenever he gets slapped — by anyone — he responds by slapping the actor he faces.

class Comedian : public Actor
{
public:
  void face(Actor* actor) { facing_ = actor; }

  virtual void update()
  {
    if (wasSlapped()) facing_->slap();
  }

private:
  Actor* facing_;
};

AI

Now, let’s throw some comedians on a stage and see what happens. We’ll set up three comedians, each facing the next. The last one will face the first, in a big circle:

Stage stage;

Comedian* harry = new Comedian();
Comedian* baldy = new Comedian();
Comedian* chump = new Comedian();

harry->face(baldy);
baldy->face(chump);
chump->face(harry);

stage.add(harry, 0);
stage.add(baldy, 1);
stage.add(chump, 2);

AI

We’ll slap Harry to get things going and see what happens when we start processing:

harry->slap();
stage.update();

Remember that the update() function in Stage updates each actor in turn, so if we step through the code, we’ll find that the following occurs:

Stage updates actor 0 (Harry)
  Harry was slapped, so he slaps Baldy
Stage updates actor 1 (Baldy)
  Baldy was slapped, so he slaps Chump
Stage updates actor 2 (Chump)
  Chump was slapped, so he slaps Harry
Stage update ends
  • In a single frame everybody is slapped!

AI

  • What if they were ordered differently:

Let’s see what happens when we run our experiment again:

Stage updates actor 0 (Chump)
  Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
  Baldy was not slapped, so he does nothing
Stage updates actor 2 (Harry)
  Harry was slapped, so he slaps Baldy
Stage update ends
  • Uh, oh. Totally different.
  • Imagine your physics engine behaving completely different based on the order you created your entities.

AI

  • Fixing it:
class Actor
{
public:
  Actor() : currentSlapped_(false) {}

  virtual ~Actor() {}
  virtual void update() = 0;

  void swap()
  {
    // Swap the buffer.
    currentSlapped_ = nextSlapped_;

    // Clear the new "next" buffer.
    nextSlapped_ = false;
  }

  void slap()       { nextSlapped_ = true; }
  bool wasSlapped() { return currentSlapped_; }

private:
  bool currentSlapped_;
  bool nextSlapped_;
};

AI

This also requires a small change in Stage:

void Stage::update()
{
  for (int i = 0; i < NUM_ACTORS; i++)
  {
    actors_[i]->update();
  }

  for (int i = 0; i < NUM_ACTORS; i++) // swap buffers
  {
    actors_[i]->swap();
  }
}

Support

  • You can find the Double Buffer pattern in use in almost every graphics API out there.
    • OpenGL has swapBuffers(),
    • Direct3D has swap chains,
    • and Microsoft’s XNA framework swaps the framebuffers within its endDraw() method.

Game Loop Pattern

Game Loop

Intent Decouple the progression of game time from user input and processor speed.

  • Game loops are the quintessential example of a game programming pattern.
    • Almost every game has one,
    • No two are exactly alike, and
    • Relatively few programs outside of games use them.

Interview with a CPU

  • We talked about Colossal Cave Adventure before.
    • The first adventure game
  • In CCA, you can sort of chat with the game, giving it commands to do. Requires a very simple implementation:
while (true) {
  char* command = readCommand();
  handleCommand(command);
}

Event Loops

  • Almost all modern UI applications work similarly.
  • They work using what we call an event loop:
while (true)
{
  Event* event = waitForEvent();
  dispatchEvent(event);
}
  • Instead of text commands, you now wait for UI events.
  • The application waits for your next input and then acts accordingly
  • However, most modern games are continous, the game world keeps updating even when you are waiting.
  • So, it is more like this:
while (true) {
  processInput(); 
  update();           // update world, run physics
  render();           // write to the framebbuffer
                      // copy framebuffer to screen
}

A world out of time

  • If the above loop is not waiting for input
    • How fast does it loop?
    • We measure this by the number of frames rendered on the screen per second
    • That is frame per second or FPS for short
  • FPS is determined by two things:
    • Power of your device
    • Complexity of the game
  • In the old times, developers targeted one architecture
    • The game ran faster on a newer machine
    • slower on an older machine.
    • This is why there used to be a TURBO button on old cases

The Pattern

A game loop runs continuously during gameplay. Each turn of the loop, it processes user input without blocking, updates the game state, and renders the game. It tracks the passage of time to control the rate of gameplay.

Implementation

  • Run as fast as you can
while (true)
{
  processInput();
  update();
  render();
}
  • Most problematic
  • Your game will simply run at different speeds based on the architecture of the device

Implementation

  • Take a little nap if you are too fast
while (true)
{
  double start = getCurrentTime();
  processInput();
  update();
  render();

  sleep(start + MS_PER_FRAME - getCurrentTime());
}
  • Tries to sleep until the next targeted frame time
  • What if we are running slowly?
    • Can’t sleep negative times!

Implementation

  • What we need is a smarter update
  • It needs to know how much time passed since the previous frame and update the world accordingly.
double lastTime = getCurrentTime();
while (true)
{
  double current = getCurrentTime();
  double elapsed = current - lastTime;
  processInput();
  update(elapsed);
  render();
  lastTime = current;
}
  • Example update of a bullet:
void update(int elapsed) {
  double dist = SpeedPerSec/1000*elapsed;
  move(dist)
}

Implementation

  • Now,
    • The game plays at a consistent rate on different hardware.
    • Players with faster machines are rewarded with smoother gameplay.
      • Better graphics but no gameplay advantage
  • Is that all? Are we done?

Implementation

  • Now,
    • The game plays at a consistent rate on different hardware.
    • Players with faster machines are rewarded with smoother gameplay.
      • Better graphics but no gameplay advantage
  • Is that all? Are we done?
    • Nope.
    • Image Ali and Ahmet playing a networked game.
    • A bullet goes through their screens at the same time.
    • Ali’s machine is 10 times faster than Ahmet’s machine
    • Ali’s world update 10 times more than Ahmet’s world
    • This accumulates floating point rounding errors
    • The bullet will end up at different positions on their machines
    • Also physics engine depends on a certain update frequency for accurate computations

Play catch up

  • Rendering is usually the heaviest part in a game
  • If it is causing your whole game to slow down
    • Why not isolate it from world updates?
double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();
}
  • Now we update as much as possible, at fixed game world intervals
  • Render once for one game loop

Play catch up

  • Note that the visible FPS and the actual game loop are different in this implementation
    • The world is updated much more than the screen
  • However, be careful not the setup MS_PER_UPDATE too small
    • Especially not smaller than you can update the world
    • Otherwise you can never catch up
  • You can even combine this with the smart update to design a smarter routine which handles the never catch up issue on really slow machines

Update Pattern

Update Pattern

Intent Simulate a collection of independent objects by telling each to process one frame of behavior at a time.

  • Consider the simplest NPC implementation of a skeleton:
Entity skeleton;

while (true)
{
  // Patrol right.
  for (double x = 0; x < 100; x++)
  {
    skeleton.setX(x);
  }

  // Patrol left.
  for (double x = 100; x > 0; x--)
  {
    skeleton.setX(x);
  }
}
  • Do you notice the problem here?

Immobile skeleton

  • To the player the skeleton never moves
    • In one frame (one game loop), it moved 100 pixels to the right, then 100 pixels to the left
    • Then the screen is refreshed and it is where it was in the previous frame
    • What we want is that the skeleton moves one step at each frame so that we can see the motion in real time
    • We must get rid of movement loops and rely on the game loop

Mobile skeleton

Entity skeleton;
bool patrollingLeft = false;
double x = 0;

// Main game loop:
while (true)
{
  if (patrollingLeft)
  {
    x--;
    if (x == 0) 
      patrollingLeft = false;
  }
  else
  {
    x++;
    if (x == 100) 
      patrollingLeft = true;
  }

  skeleton.setX(x);

  // Handle user input and render game...
}
  • This is more like it
  • But also much more complex

Some statues

  • Let’s add some statues shooting lightning at the player:
// Skeleton variables...
Entity leftStatue;
Entity rightStatue;
int leftStatueFrames = 0;
int rightStatueFrames = 0;

// Main game loop:
while (true)
{
  // Skeleton code...

  if (++leftStatueFrames == 90)
  {
    leftStatueFrames = 0;
    leftStatue.shootLightning();
  }

  if (++rightStatueFrames == 80)
  {
    rightStatueFrames = 0;
    rightStatue.shootLightning();
  }

  // Handle user input and render game...
}

Some statues and a skeleton

  • Combined together:
Entity skeleton;
bool patrollingLeft = false;
double x = 0;
Entity leftStatue;
Entity rightStatue;
int leftStatueFrames = 0;
int rightStatueFrames = 0;

// Main game loop:
while (true)
{
  if (patrollingLeft)
  {
    x--;
    if (x == 0) 
      patrollingLeft = false;
  }
  else
  {
    x++;
    if (x == 100) 
      patrollingLeft = true;
  }

  skeleton.setX(x);
  if (++leftStatueFrames == 90)
  {
    leftStatueFrames = 0;
    leftStatue.shootLightning();
  }

  if (++rightStatueFrames == 80)
  {
    rightStatueFrames = 0;
    rightStatue.shootLightning();
  }

  // Handle user input and render game...
}
  • This is clearly getting ugly and messy
  • The solution is
    • Each entity has its update code encapsulated in its class

update method

  • To do this, we need an abstraction layer
    • the game loop keeps a list of entities
    • but it doesn’t know their specific types
    • all it knows is that they can be update()d.
  • At each frame the game loop goes over the list and calls update() on each entity
    • Each entity must respectfully perform one frame’s worth of behaviour and return
    • This way all entities act simultaneously
  • Bonus you can add and remove entities to the scene on the fly, nothing is hardcoded anymore!

The Pattern

  • The game world maintains a collection of objects.
  • Each object implements an update method that simulates one frame of the object’s behavior.
  • Each frame, the game updates every object in the collection.

When to use it

  • If the Game Loop pattern is the best thing since sliced bread,
    • then the Update Method pattern is its butter.
  • However, if the game entities are moving non-continuously
    • probably update pattern is a bad fit
    • such as a chess game
  • This pattern works best, when
    • Your game has a number of objects or systems that need to run simultaneously.
    • Each object’s behavior is mostly independent of the others.
    • The objects need to be simulated over time.

Perfect world?

  • There is not perfect world
  • Main issues of this pattern
    • Slicing behavior into frames make it more complex
    • You have store state of entities to resume behavior in the next frame
      • hint: state pattern
    • Things are not truly concurrent, order matters. An object is updated before another, which may cause issues
      • hint: double buffer on entities
    • Adding or removing entities to the scene during update can cause issues
      • hint: double buffer on entity list
      • or, be extra careful when iterating the list

Final result

Variable time

  • Do you remember what we did in the game loop when time elapsed between frames varied?
    • Yes, the smarter update method
  • This is what it looks like:
void Skeleton::update(double elapsed)
{
  if (patrollingLeft_)
  {
    x -= elapsed;
    if (x <= 0)
    {
      patrollingLeft_ = false;
      x = -x;
    }
  }
  else
  {
    x += elapsed;
    if (x >= 100)
    {
      patrollingLeft_ = true;
      x = 100 - (x - 100);
    }
  }
}

End of patterns

  • There are many more useful patterns to learn
    • Component, Event Queue, Dirty Flag, Object Pool…
  • Check the book Game Programming Patterns by Robert Nystorm
  • These slides were prepared mostly by copy pasting from that book