Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Opinions wanted - SpriteManager

This thread is locked; no one can reply to it. rss feed Print
 1   2   3   4 
Opinions wanted - SpriteManager
Itachihro
Member #12,001
May 2010

Just a few classes for managing loading, creating and destroying bitmaps. I'm a bit rusty with C++ so if anyone sees some obvious mistakes / stylistic horribleness it would be nice if he/she could point it out to me.

misc.h

#pragma once
struct Position {
   int x;
   int y;
};

sprite.hpp

#SelectExpand
1#pragma once 2 3#include <memory> 4#include <string> 5#include <map> 6#include <vector> 7#include <functional> 8#include <allegro5/allegro.h> 9#include "misc.h" 10 11class SpriteBase 12{ 13public: 14 SpriteBase() = default; 15 virtual ~SpriteBase(); 16 virtual void draw(const Position&) const = 0; 17 virtual std::shared_ptr<SpriteBase> getSubSprite(const Position&, int, int) = 0; 18 virtual std::string getBitmapName() const = 0; 19}; 20 21/** 22* This class exists so that I am not forced to use 23* pointers for sprites all the time (for polymorphism) 24*/ 25class Sprite { 26private: 27 friend class SpriteManager; 28 std::shared_ptr<SpriteBase> actualSprite; 29 Sprite(std::shared_ptr<SpriteBase>&); 30public: 31 virtual void draw(const Position&) const; 32 virtual Sprite getSubSprite(const Position&, int w, int h) const; 33 virtual ~Sprite(); 34 Sprite() = delete; 35 Sprite(const Sprite&) = default; 36 virtual std::string getBitmapName() const; 37}; 38 39class SpriteManager { 40 private: 41 std::map<std::string,std::weak_ptr<ALLEGRO_BITMAP>> bitmaps; 42 public: 43 SpriteManager(); 44 ~SpriteManager() = default; 45 Sprite getSprite(const std::string& name); 46 Sprite getSubSprite(const std::string& name, const Position&, int w, int h); 47 std::vector<std::string> getCachedBitmaps() const; 48};

sprite.cpp

#SelectExpand
1#include "sprite.hpp" 2 3class BitmapSprite : public SpriteBase { 4private: 5 std::shared_ptr<ALLEGRO_BITMAP> bitmap; 6 std::string name; 7public: 8 BitmapSprite(const std::string& name,std::shared_ptr<ALLEGRO_BITMAP>& bitmap) 9 : bitmap(bitmap), name(name) {} 10 virtual void draw(const Position&) const; 11 virtual std::shared_ptr<SpriteBase> getSubSprite(const Position& pos, int w, int h); 12 virtual ~BitmapSprite() {}; 13 BitmapSprite() = delete; 14 virtual std::string getBitmapName() const; 15}; 16 17class SubSprite : public SpriteBase { 18private: 19 std::shared_ptr<ALLEGRO_BITMAP> parentBitmap; 20 std::shared_ptr<ALLEGRO_BITMAP> subBitmap; 21 std::string name; 22public: 23 SubSprite(const std::string& name,std::shared_ptr<ALLEGRO_BITMAP>& parentBitmap, 24 std::shared_ptr<ALLEGRO_BITMAP>& subBitmap) 25 :parentBitmap(parentBitmap), subBitmap(subBitmap), name(name) {} 26 virtual void draw(const Position&) const; 27 virtual std::shared_ptr<SpriteBase> getSubSprite(const Position& pos, int w, int h); 28 virtual ~SubSprite() {} 29 SubSprite() = delete; 30 virtual std::string getBitmapName() const; 31}; 32 33SpriteBase::~SpriteBase() {} 34 35Sprite::Sprite(std::shared_ptr<SpriteBase>& sprite) 36 : actualSprite(sprite) {} 37 38Sprite::~Sprite() {} 39 40void Sprite::draw(const Position& pos) const 41{ 42 actualSprite.get()->draw(pos); 43} 44 45Sprite Sprite::getSubSprite(const Position& pos, int w, int h) const 46{ 47 std::shared_ptr<SpriteBase> ptr = actualSprite.get()->getSubSprite(pos,w,h); 48 return Sprite(ptr); 49} 50 51std::string Sprite::getBitmapName() const 52{ 53 return actualSprite.get()->getBitmapName(); 54} 55 56SpriteManager::SpriteManager() 57 : bitmaps{} {} 58 59Sprite SpriteManager::getSprite(const std::string& name) { 60 auto it = bitmaps.find(name); 61 if(it!=bitmaps.end()) { 62 std::shared_ptr<ALLEGRO_BITMAP> bmp_ptr = (*it).second.lock(); 63 std::shared_ptr<SpriteBase> sprite_ptr(new BitmapSprite(name,bmp_ptr)); 64 return Sprite(sprite_ptr); 65 } else { 66 ALLEGRO_BITMAP* bmp = al_load_bitmap(name.c_str()); 67 if(!bmp) { 68 throw std::string("Failed to load image file: ") + name; 69 } 70 it = bitmaps.insert( 71 std::pair<std::string,std::weak_ptr<ALLEGRO_BITMAP>>{name, std::weak_ptr<ALLEGRO_BITMAP>()} 72 ).first; 73 std::shared_ptr<ALLEGRO_BITMAP> bmp_ptr(bmp, [it,&bitmaps](ALLEGRO_BITMAP* bmp){ 74 bitmaps.erase(it); 75 al_destroy_bitmap(bmp); 76 }); 77 bitmaps[name] = std::weak_ptr<ALLEGRO_BITMAP>(bmp_ptr); 78 std::shared_ptr<SpriteBase> sprite_ptr = std::shared_ptr<SpriteBase>(new BitmapSprite(name,bmp_ptr)); 79 return Sprite(sprite_ptr); 80 } 81} 82 83Sprite SpriteManager::getSubSprite(const std::string& name, const Position& pos, int w, int h) 84{ 85 return getSprite(name).getSubSprite(pos,w,h); 86} 87 88std::vector<std::string> SpriteManager::getCachedBitmaps() const 89{ 90 std::vector<std::string> result; 91 for(const std::pair<std::string,std::weak_ptr<ALLEGRO_BITMAP>>& p : bitmaps) { 92 result.push_back(p.first); 93 } 94 return result; 95} 96 97 98void BitmapSprite::draw(const Position& pos) const { 99 al_draw_bitmap(bitmap.get(),pos.x,pos.y,0); 100} 101 102std::shared_ptr<SpriteBase> BitmapSprite::getSubSprite(const Position& pos, int w, int h) { 103 std::shared_ptr<ALLEGRO_BITMAP> bmp_ptr(al_create_sub_bitmap(bitmap.get(),pos.x,pos.y,w,h),al_destroy_bitmap); 104 return std::shared_ptr<SpriteBase>(new SubSprite(name,bitmap,bmp_ptr)); 105} 106 107std::string BitmapSprite::getBitmapName() const 108{ 109 return name; 110} 111 112void SubSprite::draw(const Position& pos) const 113{ 114 al_draw_bitmap(subBitmap.get(),pos.x,pos.y,0); 115} 116 117std::shared_ptr<SpriteBase> SubSprite::getSubSprite(const Position& pos, int w, int h) 118{ 119 std::shared_ptr<ALLEGRO_BITMAP> bmp_ptr(al_create_sub_bitmap(subBitmap.get(),pos.x,pos.y,w,h),al_destroy_bitmap); 120 return std::shared_ptr<SpriteBase>(new SubSprite(name,parentBitmap,bmp_ptr)); 121} 122 123std::string SubSprite::getBitmapName() const 124{ 125 return name; 126}

Main file to demonstrate use:

#SelectExpand
1#include <cstdlib> 2#include <map> 3#include <vector> 4#include <iostream> 5#include <memory> 6#include <lua5.2/lua.hpp> 7#include <lua5.2/lauxlib.h> 8#include <allegro5/allegro.h> 9#include <allegro5/allegro_image.h> 10#include "misc.h" 11#include "sprite.hpp" 12 13 14void drawSomething(SpriteManager&); 15 16int main(int argc, char** argv) 17{ 18 al_init(); 19 al_init_image_addon(); 20 ALLEGRO_DISPLAY* display = al_create_display(800,600); 21 SpriteManager spriteManager; 22 drawSomething(spriteManager); 23 al_flip_display(); 24 std::cout << "***" << std::endl; 25 for(const std::string& name : spriteManager.getCachedBitmaps()) { 26 std::cout << "cached: " << name << std::endl; 27 } 28 al_rest(3); 29 al_destroy_display(display); 30} 31 32void drawSomething(SpriteManager& spriteManager) 33{ 34 try { 35 Sprite sprite = spriteManager.getSprite("img/general.png"); 36 Sprite subSprite = sprite.getSubSprite({3,90},14,14); 37 for(const std::string& name: spriteManager.getCachedBitmaps()) { 38 std::cout << "cached: " << name << std::endl; 39 } 40 std::cout << std::endl; 41 sprite.draw({100,100}); 42 subSprite.draw({600,100}); 43 } catch (const std::string& string) { 44 std::cerr << string; 45 } 46}

Stas B.
Member #9,615
March 2008

Why all those shared_ptrs? The SpriteManager should be solely responsible for creating and destroying sprites, so they're pointless. Also, you don't really want your bitmaps to be destroyed when nobody refers to them. Unless you have a ridiculous amount of data, you should preload all needed sprites when the level starts and destroy them when it ends.

Itachihro
Member #12,001
May 2010

That's what I'm doing. A level description includes the entities that appear in it, and those know which sprites they need. Upon loading a level, I'm first loading all sprites that I need from the first one, and then free those needed by the second one. Since most levels will share at least a few entities, that way I get faster load times basically for free.

Stas B. said:

The SpriteManager should be solely responsible for creating and destroying sprites, so they're pointless.

My sprite manager manages sprites. It's in the name. Except for rare exceptions, all resources are loaded at the beginning of the level and destroyed at the end of it. The SpriteManager is basically there to move the resource management logic out of the level class.

What this has to do with my shared_ptr's I didn't understand though.

Stas B.
Member #9,615
March 2008

Itachihro said:

all resources are loaded at the beginning of the level and destroyed at the end of it

Your resources die when the last shared_ptr pointing to them dies, since SpriteManager holds a weak_ptr internally. If, for instance, you have a few zombies in the level and they all die, their resources will be released. If later in the level you spawn some new zombies, the resources will be reloaded from disk. That's probably not a good thing.

Quote:

What this has to do with my shared_ptr's I didn't understand though.

You are misusing smart pointers. The whole point of shared_ptrs is to destroy an object automatically when there are no more shared_ptrs pointing to it. This is good when it's unclear who is responsible for destroying the object, since you don't want to destroy an object that may be in use by something else. This behavior violates your stated intentions and it's clear who is responsible for destroying the sprites.

Itachihro
Member #12,001
May 2010

Stas B. said:

Your resources die when the last shared_ptr pointing to them dies, since SpriteManager holds a weak_ptr internally. If, for instance, you have a few zombies in the level and they all die, their resources will be released. If later in the level you spawn some new zombies, the resources will be reloaded from disk. That's probably not a good thing.

I just told you, levels are aware of the resources that are used within them and keep them alive. The only ones for which this does not apply are temporary resources like movies etc that are only used once anyways.

Quote:

You are misusing smart pointers. The whole point of shared_ptrs is to destroy an object automatically when there are no more shared_ptrs pointing to it. This is good when it's unclear who is responsible for destroying the object, since you don't want to destroy an object that may be in use by something else. This behavior violates your stated intentions and it's clear who is responsible for destroying the sprites.

I don't see how this is a misuse. Clearly they CAN be used this way, and it achieves exactly what I want. I am flattered that you apparently like my code so well that your only complaint about it is that it doesn't meet your personal idea of handling responsibilities though.

Stas B.
Member #9,615
March 2008

Itachihro said:

levels are aware of the resources that are used within them and keep them alive.

The only way to keep those bitmaps alive is by having living shared_ptrs that point to them. Your SpriteManager can not keep the bitmaps alive because it stores weak_ptrs. You can't rely on game objects to keep them alive either because game objects can be destroyed. What keeps the bitmaps alive, then? I don't understand the statement "levels keep the resources alive". What do levels have to do with keeping resources alive?

Quote:

I don't see how this is a misuse. Clearly they CAN be used this way, and it achieves exactly what I want

If what you want is what you said you want:

Itachihro said:

all resources are loaded at the beginning of the level and destroyed at the end of it.

...then no, your class does not achieve that. Read my posts again untill you understand that.

Itachihro said:

I am flattered that you apparently like my code so well that your only complaint about it is that it doesn't meet your personal idea of handling responsibilities though.

It's not my personal idea. It's your explicit statement and it's a good one, but your code doesn't do what you think it does. As for the rest of your code, we'll get to that. Let's start by fixing the most serious and glaringly obvious mistakes.

Itachihro
Member #12,001
May 2010

Stas B. said:

...then no, your class does not achieve that. Read my posts again untill you understand that.

Of course THIS class doesn't do this, it isn't supposed to. All my SpriteManager is supposed to do is create valid sprites and ensure they and their copies are valid while in use (my implementation frees sprites the moment noone uses them anymore, but I could easily swap the implementation, for example to set a timeout before the sprites are released). That's all. The rest is handled by the classes using the SpriteManager. I think your problem is that you don't care about how I might be using this class, you just try to (mentally) insert it into your own code and find out it doesn't do what YOU want it to do.

Stas B.
Member #9,615
March 2008

I'm asking you a simple question. Since it can't be the game objects or the sprite manager, how do you make sure the bitmaps don't get destroyed prematurely? The fact that your sprite manager isn't capable of keeping the bitmaps alive on it's own is enough to send it straight to the garbage bin, but still, I'm curious.

Itachihro
Member #12,001
May 2010

Stas B. said:

I'm asking you a simple question. Since it can't be the game objects or the sprite manager, how do you make sure the bitmaps don't get destroyed prematurely? The fact that your sprite manager isn't capable of keeping the bitmaps alive on it's own is enough to send it straight to the garbage bin, but still, I'm curious.

It does keep the sprites alive for as long as I want them to. I already told you that levels are aware of the resources used within them and keep them alive, what more do you need? Your judgment is completely biased because you keep trying to shoehorn my class into usage patterns it wasn't designed for. It's a very simple premise - I am making the guarantee that if anyone has a sprite, it's valid. I'm also making the guarantee that while anyone holds a sprite, this sprite will not be reloaded. This is the intended behavior. If you don't like that, fine. But don't call my work garbage because it doesn't solve problems it isn't supposed to solve. Thank you very much.

Stas B.
Member #9,615
March 2008

Itachihro said:

I already told you that levels are aware of the resources used within them and keep them alive

If by that you mean that Level objects have a list of shared_ptrs to all their resources, that's really stupid. If you mean something else, you don't understand how smart pointers work. Either way, it doesn't even matter. You're abusing shared_ptrs, adding needless complexity and distributing responsibilities wrongly. I don't care if that's by design. Your design is really stupid. The thread says "Opinions wanted", so there you have my opinion and its explanation. This is probably the only opinion you're going to get, because everybody on this forum knows "Opinion about my code" threads are "Pat me on the back - positive opinions only" threads and people like you never learn anything from them. Have a nice day.

Itachihro
Member #12,001
May 2010

So, me being unimpressed by you calling something that works garbage and stupid without actually pointing out any concrete problems with it or offering a substantially better approach now means that I'm some whiny kid that needs confirmation? Wow.

AMCerasoli
Member #11,955
May 2010
avatar

Stas B. You have just improved your rank in my list of stupid people. Congratulations. Not because of the problem itself but because of how you express yourself.

Stas B.
Member #9,615
March 2008

Stas B. You have just improved your rank in my list of stupid people. Congratulations. Not because of the problem itself but because of how you express yourself.

You have a list of stupid people? And I'm in it? Oh noes. :'(
You're also on my list. You're a nice person but an incompetent programmer.

Itachihro said:

So, me being unimpressed by you calling something that works garbage and stupid without actually pointing out any concrete problems with it or offering a substantially better approach now means that I'm some whiny kid that needs confirmation? Wow.

I have infact pointed out concrete problems before you started with your accusations and prompted me to call your class stupid. ::)
Let me try it again. On the bottom line, you want a resource manager and you only want to create or destroy resources before the level starts. If you think about it, it makes sense for the resource manager to manage your resources. How can you manage resources? You can create them, destroy them or lend them. It makes no sense to take two thirds of those responsibilities and give them to a completely unrelated class. It's bad design and makes no semantical sense. You basically overcomplicate things needlessly (mess around with smart pointers), cause yourself some problems (how do I make sure the resources stay alive? Who do I offload the responsibility to?) and then solve them with more hacks and needless complexity. The sane thing to do is to lend ordinary pointers and do:

void onLevelLoad(...)
{
   resourceManager.refresh(resourceList);
}

Sure, there's nothing to prevent some vicious coder from calling delete on those pointers, but you can easily do the same with a smart pointer. :P

Itachihro
Member #12,001
May 2010

The smart pointers aren't exposed by SpriteManager. The sprite manager is a "stupid" class - it doesn't know anything about the context it's used it. It doesn't know there are such things as levels. It only knows it can load sprites and that it's pointless to keep sprites around if noone needs them.

If you had read my code, you would have seen that the Sprite class is the only one that's actually brought to the outside, which doesn't give anyone access to the pointers at all so I don't know why you keep talking about them as if they were anything more than an implementation detail. If I decided tomorrow that smart pointers are a ploy by the Illuminati to take over the world I could still completely remove them from all of these classes without significantly changing the behavior of these classes. Of course I'd in the end just end up re-implementing the behavior of shared_ptr if I did that though.

Stas B. said:

how do I make sure the resources stay alive? Who do I offload the responsibility to?

I don't see how your version is that much different in that regard. In both cases, the assumption is that resources stay in use while the level is loaded. The difference is that in my case the manager has the right to delete resources when noone uses them, and instead of explicitly loading and unloading everything when a new level is loaded I just tell my resource manager that my last level doesn't need it's resources anymore, and that the new level now wants some instead. Which in extreme cases can lead to no resources being loaded at all. My level descriptions aren't just the maps, but also include the kind of entities that can exist in it. I can see that you have a different idea of who should be responsible for what, but I don't see why mine should be inherently inferior to that.

Quote:

I have infact pointed out concrete problems before you started with your accusations and prompted me to call your class stupid.

What accusations? Specifically, did I accuse you of doing anything you did not?

Karadoc ~~
Member #2,749
September 2002
avatar

Where is all this vitriol coming from?

-----------

Thomas Fjellstrom
Member #476
June 2000
avatar

Where is all this vitriol coming from?

I'm not entirely sure. But Stas B. likes to call people who don't agree with him "stupid" a lot. That could be a potential source.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Stas B.
Member #9,615
March 2008

Now, that's not entirely fair to say that. I probably should not have been such a d*ck about it, but this isn't about me having a disagreement with OP. This is about OP asking for opinions, me pointing out some obvious flaws with his design and OP starting to get all defensive, telling me the flaws are actually features of his clever design and hinting that I'm only bashing them because I'm biased and narrow-minded. I'm really sorry, but that is stupid. Just read the thread and see for yourself. I like it how you're so quick to judge. :-/

Itachihro said:

My level descriptions aren't just the maps, but also include the kind of entities that can exist in it. I can see that you have a different idea of who should be responsible for what, but I don't see why mine should be inherently inferior to that.

Yours is inherently inferior because compared to the simple, obvious, no-brainer solution of returning ordinary pointers and explicitly refreshing the resources when a level begins, yours is less obvious, more complex, slower and worst of all, forces you to have an external list of resource instances. All that without offering any adventages whatsoever. It's also inferior because it makes no sense from a design and usage standpoint.

Itachihro
Member #12,001
May 2010

Adjacant maps are likely to share resources. Also, there are many resources that aren't directly related to a map. Unloading and loading everything every time a new map is load is a complete waste in this setup, and also not feasibly possible because I would have to reload resources not owned by levels as well. I create objects using prototypes, so I don't need to keep a list of resources around - that happens pretty much automatically.

Again, the problem with your criticism is that this is something intended specifically for my use, for a specific usage pattern I already had in mind, while you keep arguing that it's not fit for another usage pattern that you prefer to use.

Stas B.
Member #9,615
March 2008

I'm not going to waste any more time on this. When your euphoria of having finished your first sprite manager is over, write down the end goals of a resource manager, try to contemplate some alternative ways to achieve these goals and see how they compare with your solution in terms of complexity and ease of use. :-/

Itachihro
Member #12,001
May 2010

I understand your performance concerns because I'm introducing several layers of indirection, I still need to check if this has any noticeable effects an actual application, but ease of use? Really?

Sprite aSprite = spriteManager.getSprite("someSprite");

How is that not easy?

Also, maybe because it's because I'm inexperienced, but I don't see any particular problems with my approach the way I am intending to use it. I guess my issue is that you are criticizing my idea rather than my code. It's quite possible that this has problems I don't quite see yet, but if that's the case I still want to pursue this road until the end to see where it actually leads me.

Audric
Member #907
January 2001

In terms of performance, the levels of indirection have a negligible effect compared to what will happen when the game needs to load sprites while the game is in progress : disk access + png decoding + moving data from RAM to video RAM.

Itachihro
Member #12,001
May 2010

As I said, there won't be any sprite loading while a level is running.

Neil Walker
Member #210
April 2000
avatar

I'm not entirely sure. But Stas B. likes to call people who don't agree with him "stupid" a lot

No, I think it came from:

Itachihro said:

I'm a bit rusty with C++ so if anyone sees some obvious mistakes / stylistic horribleness it would be nice if he/she could point it out to me.

He pointed out a potential mistake/stylistic faux pas as requested but got flamed by OP for doing this.

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Thomas Fjellstrom
Member #476
June 2000
avatar

He pointed out a potential mistake/stylistic faux pas as requested but got flamed by OP for doing this.

He pointed out a personal preference and treated it as if it was the only right way of doing things, and that the OPs ideas were stupid.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Stas B.
Member #9,615
March 2008

He pointed out a personal preference and treated it as if it was the only right way of doing things, and that the OPs ideas were stupid.

A solution that is more complex, less obvious and without any practical adventages over the simple no-brainer solution, is silly in my book. I have demonstrated and explained that OP's solution is more complex and offers no practical adventages. You can argue otherwise and try to demonstrate that what I'm saying is simply false, but you can't just brush off my criticism, imply that I'm just biased and narrow-minded and expect me to be pollite to you, especially if you're the one who was asking for criticism in the first place. I like it how you seek out threads I'm active in to diss me but never offer any insight into the technical matter at hand. Go away, spammer. You are irrelevant to this discussion. ::)

 1   2   3   4 


Go to: