Problem-free Windows game is full of bugs on Linux
pryon_

I created an Asteroids clone in pure C using Allegro 5.2. I wrote most of the code on Windows with Visual Studio.

Eventually I decided to make the code platform-independent, and have it compile and run on Linux and Windows as well flawlessly. This is where the problems came in.

The game runs without any problems with max FPS on Windows compiled with MSVC (Debug configuration), but on Ubuntu with gcc 4.9.2 (--std=c99) the following bugs appear:

  • Inconsistent input handling - sometimes the ship rotates/shoots long after the turn/fire button is released, OR rotates to the opposite direction it's supposed to

  • When the ship gets hit by an asteroid for the first time, it doesn't go into "invincibility mode" for 4 seconds like it's supposed to, only for the second time.

  • Also the game is noticeably choppier than on Windows (I'm running Linux in VirtualBox, could be responsible for it)

Could you guys give me some pointers on what I did wrong that causes these runtime problems?

Check out the code here.

Thomas Fjellstrom

I haven't looked at the code, but those kinds of things sound like the game doesn't handle running slower than it's main timer. It all seems timing related, and if its running in VirtualBox, its going to run pretty slow.

pryon_

The game right now is framerate-dependent, that could be a problem.

Audric

? From what I see here, it is fixed-step logic, which should run fine on fast and slow machines :
your game loop
It uses a timer to performs the logic at a fixed interval, and skips drawing frames if it's late.

Elias

It could also be uninitialized variables. Windows tends to initialize them to zero, while in Linux they generally have random contents.

GullRaDriel

What I can say from experience: Visual Studio tend to initialize variable if you don't do it, but not all compilers do that.

When I first ported from windows to linux, I had problems with variables not being initialized to 0, pointer not being NULL without clearly stating it at declaration.

I also have some trouble with the linux version of a recorder I made. I had to set the refresh rate to something like (desktop_refresh_rate)-5 or the event queue would act strangely.

The video driver used can be at fault too. Too much time expensive drawing lead to a full event queue.

Edit: beaten by some few msecs by Elias ;-)

Dizzy Egg

Could it be down to #ifdef ALLEGRO_MSVC blocks?

pryon_

Thank you for all the answers!

As Thomas, I suspect these problems to be timing-related as well, but according to Audric, it is fixed-step logic, meaning it should behave exactly the same on both slow and fast computers.

Firstly I checked all the variables that could cause problems when left uninitialized and initialized them. I also fixed some bugs in the code, check the commit history on Bitbucket for more info.

The game is very inconsistent now - sometimes it works like it's supposed to, sometimes all the originally mentioned bugs come back (except for the choppiness, which could very well be due to running the OS in VirtualBox as Thomas mentioned). I do have to add though that the "stuck keys" problem seems to be reduced only to the spacebar now, no other controls go rogue besides that one.

There's a bug in the game that I haven't mentioned that happens every time you quit by hitting ESC on Linux:

*** Error in `./blasteroids': double free or corruption (!prev): 0x0978d7a8 ***
Aborted (core dumped)

I tried tracking it down with Valgind and GDB, but didn't succeed with either. It only pops up after hitting ESC, so it probably has to do something with the shutdown() function, although I didn't find anything in there that would cause it.

Another bug I noticed that I didn't include in the original post is that the current score seems to overflow after hitting a few asteroids, resulting in displaying a huge score suddenly that doesn't return back to its original state.

I decided to upload the Linux executable, so you don't have to go through the hassle of building the game yourself if you want to debug/run tests/etc. Debug information is included courtesy of the -g flag, look for it in the attachment.

André Silva

Yeah, this sounds like uninitialized variables to me. You can use the likes of cppcheck to help with that. Other than that, are you only rendering when the event queue is empty? If you're saying that buttons are considered held for longer than what they are, it almost sounds like the event queue is lagging behind.

pryon_

Yep, only drawing when event queue is empty.

I ran a Cppcheck scan on the Visual Studio solution but it didn't find any uninitialized variables, only stylistic issues.

Also, the stuck keys problem only concerns the spacebar now. If you download the executable and run it, you can actually try it out - just don't shoot for a few seconds and the ship will start to by itself, triggered at a random time. It's very strange because the struct that keeps track of which keys are pressed is initialized at startup (as you can see here).

I also managed to find the cause of the double free or corruption errors - when starting the game, a new thread is created and when the first thread finishes cleaning up and gets to return 0; in main, the other thread kicks in and runs the shutdown() function again, effectively free-ing twice now, thus causing the error.

This is what happens when I do thread apply all backtrace in GDB right after game_loop() returns:

Thread 3 (Thread 0xb2f64b40 (LWP 3562)):
#0  clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:103
#1  0x003d0f00 in ?? ()
#2  0x00000000 in ?? ()

Thread 2 (Thread 0xb794db40 (LWP 3556)):
#0  0xb7fdbbe0 in __kernel_vsyscall ()
#1  0xb7d622d1 in select () at ../sysdeps/unix/syscall-template.S:81
#2  0xb7f4b718 in _al_xwin_background_thread ()
   from /usr/lib/i386-linux-gnu/liballegro.so.5.2
#3  0xb7f46dfe in ?? () from /usr/lib/i386-linux-gnu/liballegro.so.5.2
#4  0xb7c691aa in start_thread (arg=0xb794db40) at pthread_create.c:333
#5  0xb7d69fde in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:122

Thread 1 (Thread 0xb794e900 (LWP 3552)):
#0  main (argc=1, argv=0xbffff0b4) at Blasteroids/main.c:187

Does anyone know what causes this new thread to be created and how can I work around it?

Peter Hull

In assign_all_objects_to_grid you calculate a bounding box for each asteroid then call assign_object_to_grid. It's possible for an object close to the edge of the screen to have (for example) x1 < 0.0 which means that min_x_tile < 0, giving you an out-of-bounds array access on line 129. This caused corruption for me which caused the game to crash elsewhere.
Hope that helps,
Pete

#SelectExpand
1void assign_object_to_grid(BoundingBox *bbox, void *object, int flag) 2{ 3 int min_x_tile = bbox->x1 / 80 - 1; 4 int min_y_tile = bbox->y1 / 80; 5 int max_x_tile = bbox->x2 / 80 - 1; 6 int max_y_tile = bbox->y2 / 80; 7 8 for (int x = min_x_tile; x <= max_x_tile; x++) 9 for (int y = min_y_tile; y <= max_y_tile; y++) 10 switch (flag) { 11 case SHIP: collisionGrid[x][y].ship = (Spaceship *) object; break; 12 case ASTEROID: collisionGrid[x][y].asteroidNode = (asteroidNode *) object; break; 13 case BLAST: collisionGrid[x][y].blast = (Blast *) object; break; 14 } 15}

[edit]
Also the static analyser says you have a use-after-free in calculate_all_asteroids - calculate_asteroid_position may destroy the memory associated with current but you then access it to get the next pointer. It may be OK in fact but worth checking.

pryon_

Very nice catch Peter!
The collision tiles accessing negative indices never crashed my game on Windows, which is very strange, but this is one of the reasons I decided to port the code and to show it to other people. I'll commit a fix soon.

Also thanks for notifying about the use-after-free bug, I actually already fixed it, just forgot to push it into the repo.

EDIT:

Well Mr. Hull, I can confirm that this was pretty much a fix-all. Adding a check for negative min_x_tile and max_x_tile variables fixed the following bugs:

  • Sudden huge score values

  • Invincibility state not lasting for the intended time

  • Shooting state getting randomly triggered

So basically the things I mentioned in the original post.
Apparently, every time I was writing into the collisionGrid array with a negative index, I overwrote some parts of the memory belonging to the game, including the current score, the invincibility ticks left and the input-handling struct's spacebar field. I wonder why none of the compilers complained about this... talk about the quirkiness of C - and my thoughtlessness. :)

The only noticeable Linux bug left is the "double free or corrupted" one with its strange threads I posted about earlier today. This code is also due for a major refactoring, especially the collision-detection implementation and the main.c file.

May I ask what static analyzer tool did you use? It seems like it would be good use for me in a future project.

Thank you for your immense help!

Thread #616708. Printed from Allegro.cc