The usefulness of bit packing
Posted: August 2nd, 2019, 10:47 am
The title is a bit misleading actually. What I'm referring to is the use of bit packing for flags and am questioning the usefulness. From a library writers perspective I'm sure they are invaluable as you have less to make especially if the flags help define behavior of the same type of object. On the other hand, it seems like it actually makes things more difficult because you have to unpack all the flags when processing a parameter anyway.
Let's do this, say you want to draw a box and of course you want to draw the box. You could store the full 32 bit color and have the user choose the color out of the 4 billion choices or you could set it up so they are limited to a simpler palette using bit flags.
With these 5 flags you can have 16 combinations. So instead of have a RED box and a YELLOW box that takes up 32 bits storing a 32 bit value for it's color, you just unpack the flags and combine a color on the fly before drawing the box.
So how this code works is
if the WHITE flag is not set, then you could get black, dark red, dark yellow, dark green, dark cyan, dark blue and purple.
If WHITE is set you could get gray, red, yellow, green, cyan, blue, magenta or white.
You as the developer save some time by not having to type out or make enums for each of the different colors and you save 3 bytes per box object by not storing the 4 bytes of color per box object. By not creating the 15-16 enums, you also don't have to write a switch statement to convert each flag into a color though there would be a lot less thought needed. Sounds like a win on some level, but what about performance? What if these flags had a different meaning? What if some combination of flags didn't make sense?
Now, let's say you wanted to have the behavior of an object be decided on flags.
Would it make sense to pack any of these flags together? Nope. Allowing the user of your code to bitwise OR these flags together is bound to end up causing issues. If you check if the character is idle and the flags are 0x1| 0x2 then the character will remain idle unless you check for both in which case the last thing check for will be it's behavior and the bug may not show up because you get what you expect for a while. Plus, when the state changes, you have to remember to clear the flag and set the new flag.
So why do I bring this up? Surely people don't do this do they?
Well, they do. Just take a look through the Win32 API. There are a metric shit ton of flags and in some cases ( probably most cases ) certain flags don't make sense to be combined and the function call will fail if you try. I know the Win32 API is outdated and can't be changed. I know from a certain point of view it's easier to just create a bunch of flags to change the behavior of an object instead of just creating different versions of the object. I'm sure in each one of those functions they have a chain of if statements checking to make sure the users ( us ) don't try anything stupid and even more if statements to direct the behavior in the way we intended it to behave.
IF THIS FLAG IS SET DO THIS
ELSE IF THIS FLAG IS SET DO THIS
ELSE IF THIS FLAG IS SET DO THIS UNLESS THIS OTHER FLAG IS SET THEN FAIL
I think it also pretty lazy to write a EnableThis() function passing in a bool ( or BOOL in C ) to determine if it should be enabled. Why not write EnableThis() and DisableThis(), nope they write a
I never realized before how frustrating it is to have to remember what combination of flags will and won't work together. Even the newer APIs like the D3D libraries still have flags and have to be used together in certain groupings or not grouped together. It forces you to refer back to the documentation, spend so much time researching and debugging and AHHH. Some if not most of this is probably because the APIs have to be compatible with the C language and that's unfortunate. C++ has much better type safety and can be more expressive. Not to mention, with RAII, it's resource safe as well.
THE C LANGUAGE NEEDS TO DIE AT THIS POINT!!!
Anyway, that's my rant for the month I hope.
Let's do this, say you want to draw a box and of course you want to draw the box. You could store the full 32 bit color and have the user choose the color out of the 4 billion choices or you could set it up so they are limited to a simpler palette using bit flags.
Code: Select all
enum ColorFlags
{
BLACK = 0x0,
RED = 0x1,
GREEN = 0x2,
BLUE = 0x4,
WHITE = 0x8
};
struct Box
{
int x, y, w, h;
char color_flags;
};
Code: Select all
void Graphics::DrawBox( Box const& box )
{
unsigned int add_white = ( box.color_flags & ColorFlags::WHITE ) != 0 ? 128 : 0;
unsigned int red = add_white, green = add_white, blue = add_white;
if( box.color_flags != 0 )
{
if( add_white != 0 )
{
red += ( box.color_flags & ColorFlags::RED ) != 0 ? 128 : 0;
green += ( box.color_flags & ColorFlags::GREEN ) != 0 ? 128 : 0;
blue += ( box.color_flags & ColorFlags::BLUE ) != 0 ? 128 : 0;
}
}
unsigned int color = ( 255 << 24 ) | ( red << 16 ) | ( green << 8 ) | blue;
for(int y = box.y; y < box.y + box.h; ++y )
{
for( int x = box.x; x < box.x + box.w; ++x )
{
PutPixel( x, y, color );
}
}
}
if the WHITE flag is not set, then you could get black, dark red, dark yellow, dark green, dark cyan, dark blue and purple.
If WHITE is set you could get gray, red, yellow, green, cyan, blue, magenta or white.
You as the developer save some time by not having to type out or make enums for each of the different colors and you save 3 bytes per box object by not storing the 4 bytes of color per box object. By not creating the 15-16 enums, you also don't have to write a switch statement to convert each flag into a color though there would be a lot less thought needed. Sounds like a win on some level, but what about performance? What if these flags had a different meaning? What if some combination of flags didn't make sense?
Now, let's say you wanted to have the behavior of an object be decided on flags.
Code: Select all
enum ActionFlags
{
IDLE = 0x1,
WALK = 0x2,
RUN = 0x4
};
struct Character
{
ActionFlags action = ActionFlags::IDLE;
};
So why do I bring this up? Surely people don't do this do they?
Well, they do. Just take a look through the Win32 API. There are a metric shit ton of flags and in some cases ( probably most cases ) certain flags don't make sense to be combined and the function call will fail if you try. I know the Win32 API is outdated and can't be changed. I know from a certain point of view it's easier to just create a bunch of flags to change the behavior of an object instead of just creating different versions of the object. I'm sure in each one of those functions they have a chain of if statements checking to make sure the users ( us ) don't try anything stupid and even more if statements to direct the behavior in the way we intended it to behave.
IF THIS FLAG IS SET DO THIS
ELSE IF THIS FLAG IS SET DO THIS
ELSE IF THIS FLAG IS SET DO THIS UNLESS THIS OTHER FLAG IS SET THEN FAIL
I think it also pretty lazy to write a EnableThis() function passing in a bool ( or BOOL in C ) to determine if it should be enabled. Why not write EnableThis() and DisableThis(), nope they write a
Code: Select all
EnableThis( BOOL bIsEnabled )
{
if( is_enabled != bIsEnabled )
is_enabled = bIsEnabled;
}
THE C LANGUAGE NEEDS TO DIE AT THIS POINT!!!
Anyway, that's my rant for the month I hope.