As mentioned in the last post about
new years resolution some generic wants that I have, I want to read books. The first book is Game Programming Patterns by Robert Nystrom. I thought about just writing the TL;DR for each pattern because I’m super lazy and everyone can read the book for free at the website. Yet somehow I ended up making a series of posts for the summary. I guess that’s what the power of January can do to you. Let’s see if it can continue ’till December. Place your bets.
Book Summary: Game Programming Patterns
- Patterns Revisited, Part 1
- Patterns Revisited, Part 2
- Sequencing Patterns
- Behavioural Patterns
- Decoupling Patterns
- Optimisation Patterns
Patterns Revisited, Part 1
The book covers some selected patterns from Design Patterns: Element of Reusable Object-Oriented Software. They are Command, Flyweight, Observer, Prototype, Singleton, and State. This post only covers the first three. The next three are over here.
The idea of Command is to turn a function call into a variable that can be passed around and stored. It’s an implementation of first-class function. The reification of a function call. Once a function call turns into an object, we can–for example, store sequential Command calls into a list, and then:
- We have a history of which Commands have been called to implement an undo/redo system.
- Make a visual editor of the list and we have our own little visual scripting system.
- We can separate the sender of the Command from the receiver in terms of time. The sender put Commands into an “event-stream” and the receiver can execute these Commands whenever.
We can combine Command with:
- Flyweight. For stateless Commands, reuse the Command instance.
- Subclass Sandbox. We may have multiple Commands that is a composition of the same atomic tasks with a different combination. Have a base class defines these tasks in protected functions. Then, the sub-classes with an “execute/invoke” function are to use the base class’ functions.
- Chain of Responsibility. In a hierarchical structure, a parent object can pawn off a Command object to the respective children.
Flyweight is commonly used in attempts to optimize data transfer from CPU to GPU. To draw a forest, we don’t load each tree mesh into the GPU memory; just the unique ones. If we have a large object that contains data, separate the large reusable data from the unique-to-instance ones. Load the large reusable data once, and have instances refer to it.
With other patterns like:
- Data Locality, we can make sure the large reusable chunk of data stays in the cache by arranging the object instances according to how memory addresses are accessed. In C++ we can use union, in C# we use StructLayoutAttribute class.
- Factory Method, we hide the object’s constructor and use a function to create instances instead. The factory function ensures the created instance refers to the large reusable data.
- Object Pool, we can save even more memory by reusing instances from a fixed pool.
- State–if the state doesn’t hold instance-specific data, we can use a static reference to a State definition object. Then, this static state definitions can be referred by hundreds of little characters that we spawn into the game world.
C# event uses this pattern. Define an
Observer/Listener/Subscriber object with something to do if an event happens, and a Subject/Publisher object that causes the event. The Subscriber adds a reference to itself and the function to be called in the event to the Publisher’s list. If the Publisher’s event happens, the Publisher iterates the list of those added Subscriber+function references and calls them one by one.
Observer pattern is kinda scary because:
- It’s synchronous. The previous Subscriber function call must finish before the next ones are called.
- Probably too much dynamic allocation. The Publisher’s list must accommodate all Subscribers.
- We must avoid the case when:
- A Subscriber is never called because it subscribes to a Publisher that is deleted.
- A Publisher tries to call a deleted Subscriber. In Unity, it’s a good practice to subscribe at
OnEnableand unsubscribe at
- Debugging is hard because it can only be done at run time. An IDE with a debugger to view step by step function calls can help in this case.
This pattern has the same idea with functional reactive programming in terms of reacting to events.
That’s a wrap for now. Next patterns to be summarized in part two are Prototype, Singleton, and State.