Archive for February, 2009

Code Snippet of the Week: Useful bitwise operations

Saturday, February 7th, 2009

As you may know, I have been working on a 3d puzzle game with some friends for the past several months. It is nearing completion which I am quite happy about. The game engine was actually written from scratch and now I am trying to throw in a plethora of features.

Originally, our game engine didn’t have a configuration file to store information about graphics and game difficulty, etc. So I used the tinyxml c++ library to enable this functionality. I had previously developed a logging system that that log various information such as the game engine’s current state and any errors that may have occurred to help us fight bugs. However the logger would log everything including general information, warnings, and errors. Although my Data logger was written to be aware of different levels or logging such as info, warnings, and errors, I had no filtering system.

I really thought a filtering system would be of use because when we release the game, I don’t really want logging enabled as it may slow down the game play. I only want logging to be enabled in case of a crash or something like that. While I am debugging the game, most of the time I don’t really care about general information such as my own graphics card information either. I would much rather just log errors and warnings while developing the game engine. Therefore I decided to implement a log filtering property in the configuration xml. I thought it might be advantageous to share how I implemented this log filtering system.

In the config.xml, I had a log property that looked like the following.

1
2
3
4
5
6
7
8
9
10
11
<log>
	<!-- 
	This allows you to specify what 
	type of logging you want.
	This is a bitwise number.
	111 = Error,Warning,Info
	0 = Everything		
	-->
	<level>5</level>
	<clear_log>true</clear_log>
</log>

The level tag is responsible for indicating what level of filtering we wish to have for the logging. Similar to the popular unix command “chmod”, you specify the level of filtering using different bit fields that have different meanings to the engine. Our engine only has 3 different types of message types: General information, Warnings, and Errors.
In my c++ code, I created several defines in the logger class.

#define MESSAGE_TYPE_INFO	1
#define MESSAGE_TYPE_WARING	2
#define MESSAGE_TYPE_ERROR	3

For ease of use, my DataLogger class is used throughout the game engine as a singleton. So when I want to add an entry into my log file, I simply get the instance of the data logger and invoke its add entry method. The add entry method takes the string we want to log as well as a message type such as (General Info = 1, Warning = 2, Error = 3). The Data Logger will output the current date and time, message type, and message. An example of what the logger would output looks like the following.

02/07/2009 01:45   INFO:	OS Version: Windows VISTA

The following is a snippet of the Data logger’s add entry method that concerns us.

1
2
3
4
5
6
7
8
9
10
11
12
int DataLogger::addEntry( char *logdata, int iMessageType)
{
	// m_chLevel represents what level of filtering
	// we want using flagged bits.
	if(m_chLevel == -1)
	{
		return 0;
	}
 
	if((m_chLevel >> (iMessageType-1) & 0x1) == 1 || m_chLevel == 0)
	{
		// Handle logging.....

The m_chLevel variable is of type “char”. The reason I elected to use a char instead of an int is because a char is exactly 1 byte which is more than enough space for us since if we want to enable everything the highest number we would use is 7. A byte can store as high a value as 255. Also, I don’t have to worry about how other machines store integers. The m_chLevel variable holds whatever value we keyed in the level tag in our xml file. So lets say we put a 5 in for our level tag. What does this mean?  If you know a little bit about the powers of 2,  it will help quite a bit.

Take of look at the following diagram

2^3 2^2 2^1 2^0
8   4   2   1

If we have a BINARY number like 0101, that would be the number 5 in decimal. We just add 4+1 which equals 5.
If we have a BINARY number like 0011, that would be the number 3 in decimal. We just add 2+1 which equals 3.
If we have a BINARY number like 1001, that would be the number 9 in decimal. We just add 8+1 which equals 9.

If you can see the pattern, then what we can do is assign the bits with arbitrary meanings. Now think of each bit as a switch that you turn on and off for different results. For example,
I decided that the least most signicant bit, 1, would mean I want to have general information logged. The second bit means I can have warnings and the third bit means I want errors logged.

Now you may ask, “Why go through all the hassle of using binary numbers and not just have a switch or if statement that filters based on a decimal value?” The reason is because then you would need to have a large switch or if statement to go through every possible combination. What if I want to filter only errors and general information or just warnings. I would need to have an if statement for every combination. Using bit switches in a binary number allows us to concoct a small efficient formula to figure out what we want to filter. In addition to the previous reasons, bitwise operations are very efficient operations and your computer can perform these operations extremely quick!

In order to understand the following formula, we need to understand one 2 more bitwise operations. The first we will cover is a bit shift. In C++ if you use the operator “>>” that means to take every bit and move if right by one.  For example,  if A = 1001 and we shift if by 1, like so

char B = A>>1;

A will now be 0100 in binary which is 4 in decimal. You can also shift to the left which is increase the number.

Finally, we need to understand the bitwise operation AND denoted by a “&”. Lets say A = 1011 and B = 1100. The AND operation will only keep the bits where both A and B are 1. So if we did the following,

char C = A&B;

C would equal 1000.

With those 2 bit operations under our belt, we are now ready to filter out our messages. So basically every time the Data Logger’s addEntry method is invoked, I want to see what type of message we are about to log. Now lets say we only want to log errors and warnings. The corresponding binary number would be, 110 which in decimal is 6. Lets say the message we are currently about to log is an warning message. What we want to do is shift the second bit all the way to the left and see if it is a 1. If it is a 1, that means that we want to log warnings and so we should log this message.

1
2
3
4
if((m_chLevel >> (iMessageType-1)&0x1) == 1 || m_chLevel == 0)
{
		// Handle logging.....
}

The first thing we do is shift our configuration level,m_chLevel, over by the message type we are about to log – 1.
So remember that m_chLevel is current equal to 110. Since we are about to log a warning, our iMessageType variable is equal to 2.
So what we are saying is 110>>2-1. That will give us the binary number 011. We are almost done however we have one small problem. The second bit which is our error bit is still turned on. We need to turn it off otherwise this message won’t be logger because 011 is 3 in decimal and not 1. The number has to be a 1 for us to log the message. So now we perform an AND operation.
011 & 001 means to set every bit to 0 except the very last bit. After performing the AND operation, we get a 1 which means it is ok to log the message because it is a warning message.

Well, that’s it really. Remember that bitwise operations may take a little cleverness from time to time but can be extremely useful for various activities. In the case of logging, bitwise operations can save you a ton of code and get the job done in a quick manner!

Was this bitwise code snippet useful to you?

View Results

Loading ... Loading ...