Putting subbitmaps in vectors
User9000

Can someone please help me fix this so it doesn't segfault? I assume I'm missing something obvious, though. I assume the issue is something I'm missing about how std::vector works, but I thought that it copies the data.

This code is based on https://wiki.allegro.cc/index.php?title=Allegro_5_Tutorial
/Input .

#SelectExpand
1#include <stdio.h> 2#include <vector> 3#include <allegro5/allegro.h> 4 5 class Al_container 6 { 7 8 public: 9 int resize(int w, int h) 10 { 11 if(this->data != NULL) { 12 al_destroy_bitmap(this->data); 13 } 14 this->data = al_create_bitmap(w,h); 15 return 0; 16 } 17 18 ALLEGRO_BITMAP *data; 19 20 Al_container() 21 { 22 this->data = NULL; 23 } 24 25 ~Al_container() 26 { 27 if(this->data != NULL) { 28 al_destroy_bitmap(this->data); 29 } 30 } 31 32 33 }; 34 35 class Image 36 { 37 public: 38 Al_container image; 39 Image() {} 40 ~Image() {} 41 }; 42 43 const float FPS = 60; 44 const int SCREEN_W = 640; 45 const int SCREEN_H = 480; 46 const int BOUNCER_SIZE = 32; 47 48 int main(int argc, char **argv) { 49 ALLEGRO_DISPLAY *display = NULL; 50 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 51 ALLEGRO_TIMER *timer = NULL; 52 ALLEGRO_BITMAP *bouncer = NULL; 53 float bouncer_x = SCREEN_W / 2.0 - BOUNCER_SIZE / 2.0; 54 float bouncer_y = SCREEN_H / 2.0 - BOUNCER_SIZE / 2.0; 55 bool redraw = true; 56 57 if(!al_init()) { 58 fprintf(stderr, "failed to initialize allegro!\n"); 59 return -1; 60 } 61 62 if(!al_install_mouse()) { 63 fprintf(stderr, "failed to initialize the mouse!\n"); 64 return -1; 65 } 66 67 timer = al_create_timer(1.0 / FPS); 68 if(!timer) { 69 fprintf(stderr, "failed to create timer!\n"); 70 return -1; 71 } 72 73 display = al_create_display(SCREEN_W, SCREEN_H); 74 if(!display) { 75 fprintf(stderr, "failed to create display!\n"); 76 al_destroy_timer(timer); 77 return -1; 78 } 79 80 bouncer = al_create_bitmap(BOUNCER_SIZE, BOUNCER_SIZE); 81 if(!bouncer) { 82 fprintf(stderr, "failed to create bouncer bitmap!\n"); 83 al_destroy_display(display); 84 al_destroy_timer(timer); 85 return -1; 86 } 87 88 al_set_target_bitmap(bouncer); 89 90 al_clear_to_color(al_map_rgb(255, 0, 255)); 91 92 al_set_target_bitmap(al_get_backbuffer(display)); 93 94 event_queue = al_create_event_queue(); 95 if(!event_queue) { 96 fprintf(stderr, "failed to create event_queue!\n"); 97 al_destroy_bitmap(bouncer); 98 al_destroy_display(display); 99 al_destroy_timer(timer); 100 return -1; 101 } 102 103 al_register_event_source(event_queue, al_get_display_event_source(display)); 104 105 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 106 107 al_register_event_source(event_queue, al_get_mouse_event_source()); 108 109 al_clear_to_color(al_map_rgb(0,0,0)); 110 111 al_flip_display(); 112 113 al_start_timer(timer); 114 115 116/// NEW 117 118 119 ALLEGRO_BITMAP *myBitmap = al_create_bitmap(96,32); 120 al_set_target_bitmap(myBitmap); 121 al_clear_to_color(al_map_rgb(0, 255, 0)); 122 al_set_target_bitmap(al_get_backbuffer(display)); 123 124 Image temp_Image; 125 std::vector<Image> new_Images; 126 127 int i; 128 for(i=0; i<3; i++) { 129 if(temp_Image.image.data != NULL) { 130 temp_Image.image.data = NULL; 131 } 132 temp_Image.image.data = al_create_sub_bitmap(myBitmap,i*32, 0, 32, 32); 133 new_Images.push_back(temp_Image); 134 135 } 136 137/// TO HERE 138 139 while(1) { 140 ALLEGRO_EVENT ev; 141 al_wait_for_event(event_queue, &ev); 142 143 if(ev.type == ALLEGRO_EVENT_TIMER) { 144 redraw = true; 145 } else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 146 break; 147 } else if(ev.type == ALLEGRO_EVENT_MOUSE_AXES || 148 ev.type == ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY) { 149 150 bouncer_x = ev.mouse.x; 151 bouncer_y = ev.mouse.y; 152 } else if(ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) { 153 break; 154 } 155 156 if(redraw && al_is_event_queue_empty(event_queue)) { 157 redraw = false; 158 159 al_clear_to_color(al_map_rgb(0,0,0)); 160 161/// NEW 162 al_draw_bitmap(new_Images.at(0).image.data, 0, 0, 0); 163 al_draw_bitmap(new_Images.at(1).image.data, 32+32, 0, 0); 164 al_draw_bitmap(new_Images.at(2).image.data, 64+32, 0, 0); 165/// 166 al_draw_bitmap(bouncer, bouncer_x, bouncer_y, 0); 167 168 al_flip_display(); 169 } 170 } 171 172/// NEW 173 al_destroy_bitmap(myBitmap); 174/// 175 al_destroy_bitmap(bouncer); 176 al_destroy_timer(timer); 177 al_destroy_display(display); 178 al_destroy_event_queue(event_queue); 179 180 return 0; 181 }

Erin Maus

std::vector will invoke the copy constructor (in your case, it is a simple memberwise copy because you don't implement a custom copy constructor), and then delete the original, when adding elements (because the backing array needs resized). Your destructor deallocates the image that the default copy constructor just copied.

Perhaps the easiest way is to fix this is make a std::vector of pointers to images. You'll have to manually deallocate the images, however, when necessary.

User9000

Is there a way to use the al_clone_bitmap thing?

I want dinit to be self managed. What about a stateful funtor holding the vector of Bitmaps that shuts itself down at the end?

Edgar Reynaldo

Yes, you can make a deep copy of an ALLEGRO_BITMAP by using al_clone_bitmap, but why do you want to make copies of your images? It takes time and cpu and memory. You'd be better off using a vector of pointers to images that you add using new and destroy using delete.

User9000

ALLEGRO_BITMAP * newedBitmap = new ALLEGRO_BITMAP;

fails with 2 errors:

#SelectExpand
2error: invalid use of incomplete type ALLEGRO_BITMAP {aka struct ALLEGRO_BITMAP} 3error: forward declaration of ALLEGRO_BITMAP {aka struct ALLEGRO_BITMAP}

So I assume I can't actually new / delete them. :/

Also, what happens when you al_create_bitmap twice to the same pointer?

Edgar Reynaldo

You can't create an ALLEGRO_BITMAP with new. You have to use one of the allegro functions. ALLEGRO_BITMAP is also an opaque struct so you can't access its members.

You can however new your Image class.

User9000

Wait. Why do I need to access its members to new? And rather, why would encapsulating it make this any better and work?

Thomas Fjellstrom

You don't need to access it's members. But the way that you make a struct opaque in C also means you can't access it's members or new/malloc it, since you don't know its size.

Encapsulating it makes it so people can't access the internals, and make it impossible for us to change internal things when we need to.

Also new wouldn't know how to "create" an ALLEGRO_BITMAP. an ALLEGRO_BITMAP is a C type, that requires certain members to be set in a specific way. C does not have constructors, or proper objects/classes for that matter, so you have to use functions that know how to create them. ie: al_create_bitmap.

User9000

Ok.

Why doesn't allegro internally have a linked list that gets added to on al_create_bitmap and is used during allegro's deinit such that al_destroy_bitmap isn't required?

Audric

al_destroy_bitmap() would be needed anyway, because graphic memory is a precious resource shared by your entire system.
If your game has very few graphics (ex: a Sokoban with many maps but always the same tiles), you can probably load all graphics at beginning and keep them until the game stops.
But for example a game with different graphics for each level (Ice world, Lava World, Haunted house etc.) should rather keep only one level in memory. While the graphics of the player character can be kept for the whole duration of the program.

Arthur Kalliokoski

Steam hardware survey says the average video card has 1Gb of ram.

http://store.steampowered.com/hwsurvey/

Audric

If you use truetype font and "leak" the bitmap of the text every time you redraw the screen, you will run out, just by leaving the game idle.

beoran

My suggestion, as always, would be to use plain C and not C++ until you thoroughly understand both of them. Allegro is a C library, so you should understand C to use it. If you just know C++, then you don't know enough C to use Allegro, as the essential design of the two languages is fundamentally different.

C++ vectors require you to have a deep understanding of C++ to be able to use them correctly in all cases. C++ is often like that, on the face it looks easy to use, but to do anything that isn't a very simple construct correctly you have to know many fine details of C++.

Below and attached is a pure C solution that doesn't use any vectors but that makes specific types for games, namely Sprite and SpriteList. It's a bit longer than the C++ code, but now you have full control of what is going on, as well as the beginnings of a generic sprite list system that will be useful for many types of games. Not to mention that since C doesn't do anything behind your back, so it's easy to see what is going on.

This Spritelist also keeps track of all bitmaps allocated indirectly, so just calling spritelist_done will clean up the allocated sprites for you.

#SelectExpand
1#include <stdio.h> 2#include <stdlib.h> 3#include <allegro5/allegro.h> 4 5const int FPS = 60; 6const int SCREEN_W = 640; 7const int SCREEN_H = 480; 8const int BOUNCER_SIZE = 32; 9 10const int SPRITELIST_START_SIZE = 1000; 11const int SPRITELIST_INCREMENT = 100; 12 13/* Enum for different statuses for return values of functions that can fail. */ 14enum GameStatus { 15 GameStatusOK = 0, 16 GameStatusNoMemory = -1, 17 GameStatusBadArgument = -2, 18 GameStatusAllegroError= -3, 19}; 20 21 22/* A set of x and Y coordinaes. */ 23struct Point { 24 float x; 25 float y; 26}; 27 28struct Point point_make(float x, float y) { 29 struct Point point; 30 point.x = x; 31 point.y = y; 32 return point; 33} 34 35struct Point point_add(struct Point self, struct Point other) { 36 return point_make(self.x + other.x, self.y + other.y); 37} 38 39/* A sprite keeps thack of the visualisation, position and speed of an in-game entity. */ 40struct Sprite { 41 ALLEGRO_BITMAP * bitmap; 42 struct Point position; 43 struct Point speed; 44}; 45 46void sprite_init(struct Sprite * self, ALLEGRO_BITMAP * bitmap, 47 int x, int y, int vx, int vy) { 48 self->bitmap = bitmap; 49 self->position = point_make(x, y); 50 self->speed = point_make(vx, vy); 51} 52 53void sprite_done(struct Sprite * self) { 54 al_destroy_bitmap(self->bitmap); 55 self->bitmap = NULL; 56} 57 58 59/* 60 We don't need a generic vector. We need a list of sprites to draw. 61*/ 62struct SpriteList { 63 struct Sprite * sprites; 64 int size; 65 int in_use; 66}; 67 68 69/* Initialize the sprite list. */ 70int spritelist_init(struct SpriteList * self) { 71 self->in_use = 0; 72 self->size = SPRITELIST_START_SIZE; 73 self->sprites = calloc(self->size, sizeof(struct Sprite)); 74 if (!self->sprites) { 75 self->size = 0; 76 return GameStatusNoMemory; 77 } 78 return GameStatusOK; 79} 80 81 82 83/* Grows the sprite list */ 84int spritelist_grow(struct SpriteList * self, int grow_by) { 85 struct Sprite * new_list; 86 if (grow_by < 1) return GameStatusBadArgument; 87 new_list = realloc(self->sprites, self->size + grow_by); 88 if(!new_list) return GameStatusNoMemory; 89 self->size += grow_by; 90 self->sprites = new_list; 91 return GameStatusOK; 92} 93 94/* Gets a pointer to a sprite in the list. */ 95struct Sprite * spritelist_get(struct SpriteList * self, int index) { 96 if (index < 0) return NULL; 97 if (index > self->size) return NULL; 98 return self->sprites+index; 99} 100 101/* Cleans up the sprite list. */ 102void spritelist_done(struct SpriteList *self) { 103 int index; 104 for (index = 0 ; index < self->in_use; index++) { 105 struct Sprite * sprite = spritelist_get(self, index); 106 sprite_done(sprite); 107 } 108 free(self->sprites); 109 self->in_use = 0; 110 self->size = 0; 111} 112 113 114/* Adds a sprite to the list. returns the index at which it is , or a negative value if it failed to ad the item to the list. */ 115int spritelist_add_sprite(struct SpriteList * self, ALLEGRO_BITMAP * bitmap, 116 int x, int y, int vx, int vy) { 117 int result; 118 struct Sprite * sprite; 119 if(self->in_use >= self->size) { 120 int res = spritelist_grow(self, SPRITELIST_INCREMENT); 121 if (res != GameStatusOK) return res; 122 } 123 sprite = spritelist_get(self, self->in_use); 124 sprite_init(sprite, bitmap, x, y, vx, vy); 125 result = self->in_use; 126 self->in_use++; 127 return result; 128} 129 130/* Adds a sprite to the list and also allocate a bitmap for it with the given sizes. */ 131int spritelist_add_sprite_with_size(struct SpriteList * self, int bitmap_w, int bitmap_h, int x, int y, int vx, int vy) { 132 ALLEGRO_BITMAP * bitmap; 133 bitmap = al_create_bitmap(bitmap_w, bitmap_h); 134 if (!bitmap) return GameStatusAllegroError; 135 return spritelist_add_sprite(self, bitmap, x, y, vx, vy); 136} 137 138/* Adds a sprite to the list and also allocate a bitmap for it with the given size and color. */ 139int spritelist_add_sprite_with_size_and_color(struct SpriteList * self, int bitmap_w, int bitmap_h, int r, int g, int b, int x, int y, int vx, int vy) { 140 ALLEGRO_BITMAP * bitmap; 141 bitmap = al_create_bitmap(bitmap_w, bitmap_h); 142 if (!bitmap) return GameStatusAllegroError; 143 al_set_target_bitmap(bitmap); 144 al_clear_to_color(al_map_rgb(r, g, b)); 145 al_set_target_bitmap(al_get_backbuffer(al_get_current_display())); 146 return spritelist_add_sprite(self, bitmap, x, y, vx, vy); 147} 148 149/* Draws all sprites in the list. */ 150void spritelist_draw(struct SpriteList * self) { 151 int index; 152 for (index = 0; index < self->in_use; index++) { 153 struct Sprite * sprite = spritelist_get(self, index); 154 if (sprite && sprite->bitmap) { 155 al_draw_bitmap(sprite->bitmap, sprite->position.x, sprite->position.y, 0); 156 } 157 } 158} 159 160/* Updates the position of all sprites in the list based on their speed. */ 161void spritelist_update(struct SpriteList * self) { 162 int index; 163 for (index = 0; index < self->in_use; index++) { 164 struct Sprite * sprite = spritelist_get(self, index); 165 if (sprite) { 166 sprite->position = point_add(sprite->position, sprite->speed); 167 } 168 } 169} 170 171 172 173 174/* A struct to kep all the game state in. */ 175struct GameState { 176 ALLEGRO_DISPLAY * display; 177 ALLEGRO_EVENT_QUEUE * event_queue; 178 ALLEGRO_TIMER * timer; 179 struct SpriteList spritelist; 180 int busy; 181 int redraw; 182}; 183 184 185 186/* Initializes the game state. */ 187int gamestate_init(struct GameState * self, int screen_w, int screen_h) { 188 int res; 189 190 if(!al_init()) { 191 fprintf(stderr, "Failed to initialize Allegro!\n"); 192 return GameStatusAllegroError; 193 } 194 if(!al_install_mouse()) { 195 fprintf(stderr, "Failed to initialize the mouse!\n"); 196 return GameStatusAllegroError; 197 } 198 self->timer = al_create_timer(1.0 / FPS); 199 if(!self->timer) { 200 fprintf(stderr, "Failed to create timer!\n"); 201 return GameStatusAllegroError; 202 } 203 204 self->display = al_create_display(screen_w, screen_h); 205 if(!self->display) { 206 fprintf(stderr, "Failed to create display!\n"); 207 al_destroy_timer(self->timer); 208 return GameStatusAllegroError; 209 } 210 211 self->event_queue = al_create_event_queue(); 212 if(!self->event_queue) { 213 fprintf(stderr, "Failed to create event_queue!\n"); 214 al_destroy_display(self->display); 215 al_destroy_timer(self->timer); 216 return GameStatusAllegroError; 217 } 218 219 al_register_event_source(self->event_queue, 220 al_get_display_event_source(self->display)); 221 al_register_event_source(self->event_queue, 222 al_get_timer_event_source(self->timer)); 223 al_register_event_source(self->event_queue, 224 al_get_mouse_event_source()); 225 226 227 res = spritelist_init(&self->spritelist); 228 if(res != GameStatusOK) { 229 al_destroy_timer(self->timer); 230 al_destroy_display(self->display); 231 al_destroy_event_queue(self->event_queue); 232 return GameStatusAllegroError; 233 return res; 234 } 235 236 al_start_timer(self->timer); 237 238 self->busy = 1; 239 self->redraw = 0; 240 241 return GameStatusOK; 242} 243 244 245/* Cleans up the game state. */ 246void gamestate_done(struct GameState * self) { 247 al_destroy_timer(self->timer); 248 al_destroy_display(self->display); 249 al_destroy_event_queue(self->event_queue); 250 spritelist_done(&self->spritelist); 251 self->timer = NULL; 252 self->display = NULL; 253 self->event_queue = NULL; 254 self->busy = 0; 255 self->redraw = 0; 256}; 257 258 259 260/* Handles a single loop when running the game state. Here all the real action happens. */ 261void gamestate_runloop(struct GameState * self) { 262 struct Sprite * bouncer; 263 ALLEGRO_EVENT ev; 264 al_wait_for_event(self->event_queue, &ev); 265 switch(ev.type) { 266 case ALLEGRO_EVENT_TIMER: 267 self->redraw = 1; break; 268 269 case ALLEGRO_EVENT_MOUSE_BUTTON_UP: 270 case ALLEGRO_EVENT_DISPLAY_CLOSE: 271 self->busy = 0; break; 272 273 case ALLEGRO_EVENT_MOUSE_AXES: 274 case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY: 275 bouncer = spritelist_get(&self->spritelist, 0); 276 bouncer->position.x = ev.mouse.x; 277 bouncer->position.y = ev.mouse.y; 278 break; 279 default: 280 break; 281 } 282 /* Update and redraw if needed. */ 283 if(self->redraw && al_is_event_queue_empty(self->event_queue)) { 284 spritelist_update(&self->spritelist); 285 self->redraw = 0; 286 al_clear_to_color(al_map_rgb(0,0,0)); 287 spritelist_draw(&self->spritelist); 288 al_flip_display(); 289 } 290} 291 292 293/* Runs the game state by looping it using gamestate_runloop */ 294void gamestate_run(struct GameState * self) { 295 while(self->busy) { 296 gamestate_runloop(self); 297 } 298} 299 300 301/* Adds a few sprites to the game state. */ 302int gamestate_addsprites(struct GameState * gamestate) { 303 int res; 304 float bouncer_x = SCREEN_W / 2.0 - BOUNCER_SIZE / 2.0; 305 float bouncer_y = SCREEN_H / 2.0 - BOUNCER_SIZE / 2.0; 306 307 res = spritelist_add_sprite_with_size_and_color( 308 &gamestate->spritelist, 32, 32, 255, 0, 255, 309 bouncer_x, bouncer_y, 0, 0); 310 if (res < 0) return res; 311 res = spritelist_add_sprite_with_size_and_color( 312 &gamestate->spritelist, 32, 64, 255, 255, 313 0, 0, 0, 1, 1); 314 if (res < 0) return res; 315 /* Etc, add more sprites here if needed.... */ 316 return GameStatusOK; 317} 318 319/* The main function is now much simplified. */ 320int main(int argc, char **argv) { 321 struct GameState gamestate; 322 323 if (gamestate_init(&gamestate, SCREEN_W, SCREEN_H) != GameStatusOK) { 324 return 1; 325 } 326 if (gamestate_addsprites(&gamestate) != GameStatusOK) { 327 gamestate_done(&gamestate); 328 return 1; 329 } 330 gamestate_run(&gamestate); 331 gamestate_done(&gamestate); 332 return 0; 333}

User9000

Got it working with C++ now. It has to be C++ because I do a lot with generic programming. Thanks!

PS. The C code in interesting and might be very useful for a different project. Is that code under the Allegro License?

beoran

Well, personally I don't need generics or C++ for anything, but if you got it to work then that's fine.

As for this C code, it's only a snippet, so feel free to use, modify, sublicense, etc. it in any way you like, under the Allegro license or any other open source license you might think of.

However, if you want a full tile and sprite engine look at my project here:
https://github.com/beoran/eruta

That is under the Sleepycat license, which is a copyleft, but simpler than GPL. You can do what you want but if you redistribute a modified binary version, you also have to redistribute the modified sources of the engine.

Thread #614421. Printed from Allegro.cc