Code Sample: PMAN – A Persistent Memory Version of the Game Pac-Man

ID 标签 686953
已更新 7/5/2018
版本 Latest
公共

author-image

作者

File(s): Download
License: 3-Clause BSD License
Optimized for...  
OS: Linux* kernel version 4.3 or higher
Hardware: Emulated: How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM)
Software:
(Programming Language, tool, IDE, Framework)
C++ Compiler and Persistent Memory Developers Kit (PMDK)
Prerequisites: Familiarity with C++

Overview

User interface for a Pac-Man type gamePMAN is a game of Pac-Man that showcases the benefits of persistent memory through the use of transactions, persistent memory pointers, and persistent memory pools. PMAN's objects are stored in persistent memory, allowing the game to be safely killed and then resumed in the same state. For example, in an unfortunate circumstance like power failure, users can resume the game. In this example, we go over what makes PMAN a persistent game by covering the code, discussing the implementation of persistent memory concepts, and showing you how to run the PMAN game. 

 

Persistent Memory

This article assumes that you have a basic understanding of persistent memory (PMEM) concepts and are familiar with features of the Persistent Memory Development Kit (PMDK). If not, visit the Intel® Developer Zone Persistent Memory Programming site, where you'll find the information you need to get started. Read further to learn how persistent memory was used to develop PMAN, the persistent version of the game of Pac-Man.

PMAN Game Design

Let's go over the structure of the PMAN code. There are seven classes in total: Point, Bomb, Player, Alien, Intro, Board_State, and State. Each class contains its own functions, but the main classes we will look at include:

  • State: This class keeps track of the state of the game. It contains the functions that resume/reset the game, handle moves and collisions, and prints the "start" and "game over" messages.
  • Board_State: This class keeps track of the objects on the board and the size of the field. It contains functions that keep track of elements and their positions, whether a player is dead, and whether a bomb explodes (bombs can be placed anywhere on the board and give you all the points available in the row where you place it).
  • Player: This class is responsible for moving the player around the map. It contains the function progress, which changes the direction of the player element given the user input.

Structure and UML* Diagram

Designing the structure and types of objects that will be persistent is vital for creating PMEM-aware code with libpmemobj++. The persistent data structure is created by first determining a root object. In this case the state class is the root, meaning it is unique and essential to reach all the other objects created in the program. We discuss the state class and what makes it the root in detail later on. For the PMAN code sample we use the following persistent data structure.

persistent data structure map

Game Execution

In the main function of PMAN.cpp you can see how the initialization logic of PMAN works. The first thing it does is to check that the number of arguments, as well as its order, is correct. The following snippet shows the correct format necessary for invoking the game: It requires the game file name and gives the option to enter a map file if you have something other than the default map:

./pman <game session file> [map file]

In the next few lines it checks whether the game session file specified previously already exists. One of two things can happen from here. Either the file entered exists and the game runs using the data structures within it (by first memory mapping the file), or the file doesn't exist and the game creates a new one (also initializing a new game with fresh new data structures).

The code snippet below shows how this happens. In this case the pool checks the LAYOUT_NAME stored in state and checks whether it matches the game file entered.

if (pool<examples::state>::check(name, LAYOUT_NAME) == 1)
	pop = pool<examples::state>::open(name, LAYOUT_NAME);
else
	pop = pool<examples::state>::create(
		name, LAYOUT_NAME, PMEMOBJ_MIN_POOL * 2);

Now that we know how the game starts to execute, let's go over the three main classes that we identified earlier. Throughout each of the three classes we will go over what makes items in each persistent.

State Class

The State class is the root object, as we discussed earlier, while showing the Unified Modeling Language* (UML*) diagram of the program. The root object connects all the other objects inside a persistent memory pool. This way, if the memory range location of the pool changes, you can still access all the other objects from the root. In PMAN (as well as in other persistent memory code samples using libpmemobj++), the pool object pointer (POP) is the object used to interface with a memory pool. We can use POP to get a pointer to the root object, which in PMAN is State:

persistent_ptr<examples::state> r = pop.get_root();

If you want a refresher or want to read more on pools and root objects, check out the introduction to libpmemobj tutorial in pmem.io.

The first function inside the State class is state::init(), which is used to initialize the game. In this function we encounter the first transaction.

Transactions are a way to secure data structures from corruption that could happen as a result of an interruption such as power failure. It does this by rolling back the changes after an interruption occurs. There are three types of transactions: closure, manual, and automatic. These transactions have their own subtle differences; for example, closure transactions automatically commit or abort while manual transactions require a manual commit at the end. We see manual transactions throughout the PMAN code sample. More details about transactions and how they are implemented can be found in the C++ bindings for libpmemobj (part 6) – transactions on pmem.io.

The transaction inside state::init() is manual—meaning it must be committed at its end (Transaction::commit()). This transaction is protecting the process of initializing the game points and direction. In this case, intro_p is a list of objects used in the game. The transaction ensures that the game does not end up half-initialized.

{
transaction::manual tx(pop);
if (intro_p->size() == 0) {
	for (int i = 0; i < SIZE / 4; i++) {
		intro_p->push_back(
			make_persistent<intro>(i, i, DOWN));
		intro_p->push_back(make_persistent<intro>(
			SIZE - i, i, LEFT));
		intro_p->push_back(make_persistent<intro>(
			i, SIZE - i, RIGHT));
		intro_p->push_back(make_persistent<intro>(
			SIZE - i, SIZE - i, UP));
		}
	}
	Transaction::commit();
}

Board_State class

The board_state class starts off with a constructor for the class that initializes the "board" (map) for the game of Pac-Man and the necessary variables. The constructor allocates an array of field elements of size SIZE*SIZE on persistent memory, returning a persistent pointer object to it. The persistent pointer object is then assigned to the field board_state::board.

board_state::board_state(const std::string &map_file) : highscore(0){
	reset_params();
	board = make_persistent<field[]>(SIZE * SIZE);
	board_tmpl = make_persistent<field[]>(SIZE * SIZE);
	for (int i = 0; i < SIZE * SIZE; ++i)
		set_board_elm(i, 0, FREE);
	set_board(map_file);
}

This allocation is done as part of the construction of board_state, which happens inside the method state::new_game. This method uses a transaction, meaning that if it aborts, the allocation is rolled back and memory is returned to its original state. It is worth mentioning here that make_persistent fails if not invoked inside a transaction. You can learn more in depth about the make_persistent function by reading C++ bindings for libpmemobj (part 5) – make_persistent.

The memory allocated in the constructor above and pointed to by the pointer for the objects board and board_tmpl is then freed using a destructor that calls delete_persistent, as shown below:

board_state::~board_state(){
	delete_persistent<field[]>(board, SIZE * SIZE);
	delete_persistent<field[]>(board_tmpl, SIZE * SIZE);
}

Player class

The player class contains the function player::progress that checks the input from the keyboard and uses that input to set the proper direction for the player in the game. In addition to setting the direction of the player, it allows you to place bombs that will blow up the entire row where the player is and provide you with the points available within that row. You can see how the function uses a switch case to perform different actions based on the input provided by looking at the code snippet below:

player::progress(int in, bomb_vec *bombs){
	switch (in) {
			case KEY_LEFT:
			case 'j':
				dir = LEFT;
				break;
			case KEY_RIGHT:
			case 'l':
				dir = RIGHT;
				break;
			case KEY_UP:
			case 'i':
				dir = UP;
				break;
			case KEY_DOWN:
			case 'k':
				dir = DOWN;
				break;
			case KEY_SPACE:
			case 'b':
				dir = STOP;
				if ((*bombs)->size() <= MAX_BOMBS)
					(*bombs)->push_back(make_persistent<bomb>(x, y));
				break;
	}
	move();
dir = STOP; }

Once the direction (dir) is set, the point::move function in the point class moves the object in that direction by adjusting position points.

Running the Game

Now that you have a general sense of how the PMAN game works and the knowledge of the specific elements that make the game persistent, you can build and run the game.

First, download and build PMDK. Installation assistance, including dependencies, can be found in the PMDK Readme.

After doing so you can see that the gameFile is where the game session is stored. This is either created the first time you play, or you can open a game file where you previously played. If this is your first time, make up a name for your file and start the game like this:

$ ./pman firstGame

Controls

The PMAN game launches as soon as you type that command and now you can start playing the game. To start the game press s. To quit press q. In the game the player can be controlled either by the ARROW key or similarly by the j (left), l (right), i (up), and k (down) keys. The game also implements the bomb, which you can trigger by pressing the space bar or b. A bomb gets you points for the entire row or column until it hits a wall—but be careful, for they can kill you too. You can press q any time to quit the game. You can always resume the game by using the same file name to launch it.

Game Objective

The goal of this game is to get the highest score by guiding Pac-Man to consume all the dots and other items to win points and move up the levels.

Summary

The goal of the PMAN code sample is to introduce you to the persistent memory programming model, so that you can see what additions make a program persistent, allowing you to apply them to your developments. We introduced you to examples of persistent memory pools, pointers, and transactions, and provided additional links so that you could learn more about each topic.

Let's go over briefly what we saw in the PMAN code sample. The State class was the root object that anchors all the other objects inside a persistent memory pool. PMAN also used transactions throughout the State class to protect the data structure from corruption. We introduced the make_persistent and delete_persistent functions with respect to pointers and we covered persistent pointers. If you're interested in learning more, I encourage you to dig deeper into PMAN, or explore our other code samples on software.intel.com or in our GitHub* repo. Also visit our other PMDK examples on GitHub.

"