Tags

  • AWS (7)
  • Apigee (3)
  • ArchLinux (5)
  • Array (6)
  • Backtracking (6)
  • BinarySearch (6)
  • C++ (19)
  • CI&CD (3)
  • Calculus (2)
  • DesignPattern (43)
  • DisasterRecovery (1)
  • Docker (8)
  • DynamicProgramming (20)
  • FileSystem (11)
  • Frontend (2)
  • FunctionalProgramming (1)
  • GCP (1)
  • Gentoo (6)
  • Git (15)
  • Golang (1)
  • Graph (10)
  • GraphQL (1)
  • Hardware (1)
  • Hash (1)
  • Kafka (1)
  • LinkedList (13)
  • Linux (27)
  • Lodash (2)
  • MacOS (3)
  • Makefile (1)
  • Map (5)
  • MathHistory (1)
  • MySQL (21)
  • Neovim (10)
  • Network (66)
  • Nginx (6)
  • Node.js (33)
  • OpenGL (6)
  • PriorityQueue (1)
  • ProgrammingLanguage (9)
  • Python (10)
  • RealAnalysis (20)
  • Recursion (3)
  • Redis (1)
  • RegularExpression (1)
  • Ruby (19)
  • SQLite (1)
  • Sentry (3)
  • Set (4)
  • Shell (3)
  • SoftwareEngineering (12)
  • Sorting (2)
  • Stack (4)
  • String (2)
  • SystemDesign (13)
  • Terraform (2)
  • Tree (24)
  • Trie (2)
  • TwoPointers (16)
  • TypeScript (3)
  • Ubuntu (4)
  • Home

    Game Programming Patterns - Observer

    Published Oct 17, 2019 [  DesignPattern  ]

    Observer pattern lets one piece of code announce that something interesting happened without actually caring who receives the notification.

    To implement the “Fall off a Bridge” badge

    void Physics::updateEntity(Entity& entity)
    {
      bool wasOnSurface = entity.isOnSurface();
      entity.accelerate(GRAVITY);
      entity.update();
      if (wasOnSurface && !entity.isOnSurface())
      {
        notify(entity, EVENT_START_FALL);
      }
    

    All it does is say, “Uh, I don’t know if anyone cares, but this thing just fell. Do with that as you will.”

    Decoupling

    The physics engine does have to decide what notification to send, so it isn’t entirely decoupled. But in architecture, we’re most often trying to make systems better, not perfect.

    The achievement system register itself so that whenever the physics code sends a notification, the achievement system receives it.

    How it Works

    The observer

    class Observer
    {
    public:
      virtual ~Observer() {}
      virtual void onNotify(const Entity& entity, Event event) = 0;
    };
    

    Parameters to onNotify

    The parameters to onNotify() are up to you. Typical parameters are the object that sent the notification and a generic “data” parameter you stull other details into.

    The achievement system

    class Achievements : public Observer
    {
    public:
      virtual void onNotify(const Entity& entity, Event event)
      {
        switch (event)
        {
        case EVENT_ENTITY_FELL:
          if (entity.isHero() && heroIsOnBridge_)
          {
            unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);
          }
          break;
    
          // Handle other events, and update heroIsOnBridge_...
        }
      }
    
    private:
      void unlock(Achievement achievement)
      {
        // Unlock if not already unlocked...
      }
    
      bool heroIsOnBridge_;
    };
    

    The subject

    The notification method is invoked by the object being observed. In Gang of Four parlance, that object is called the “subject”. It has two jobs. First, it holds the list of observers that are waiting for a missive from it:

    class Subject
    {
    private:
      Observer* observers_[MAX_OBSERVERS];
      int numObservers_;
    };
    
    

    The important bit is that the subject exposes a public API from modifying that list:

    class Subject
    {
    public:
      void addObserver(Observer* observer)
      {
        // Add to array...
      }
    
      void removeObserver(Observer* observer)
      {
        // Remove from array...
      }
    
      // Other stuff...
    };
    

    The other job of the subject is sending notification:

    class Subject
    {
    protected:
      void notify(const Entity& entity, Event event)
      {
        for (int i = 0; i < numObservers_; i++)
        {
          observers_[i]->onNotify(entity, event);
        }
      }
    
      // Other stuff...
    };
    

    Modification

    Note that this code assumes observers don’t modify the list in their onNotify() methods. A more robust implementation would either prevent or gracefully handle concurrent modification like that.

    Observable physics

    Now, we just need to hook all of this into the physics engine so that it can send notifications and the achievement system can wire itself up to receive them.

    class Physics : public Subject
    {
    public:
      void updateEntity(Entity& entity);
    };
    

    In real code

    In real code, I would avoid using inheritance here. Instead, I’d make Physics have an instance of Subject. Instead of observing the physics engine itself, the subject would be a separate “falling event” object. Observers could register themselves using something like:

    physics.entityFell()
      .addObserver(this);
    

    To me, this is the difference between “observer” systems and “event” systems. With the former, you observer the thing what did something interesting. With the latter, you observe an object that represents “the interesting thing that happened”.

    References: