Sign in to follow this  
Mybowlcut

Resource life issue

Recommended Posts

I have an issue involving the surface of some text that is passed into a queue to be rendered going out of scope before it is rendered. In SDL, surfaces represent both images loaded from file and text. Every Text object has a surface that is the actual pixels representing the text:
typedef boost::shared_ptr<SDL_Surface> surface_ptr;

class Text
{
    // ...
    surface_ptr surface;
};

Caching surfaces for Text would not make sense since there are so many potential string combinations, so I made the decision to let surfaces be destroyed when they are no longer used and that is why they wrapped in a shared_ptr. On the other hand, images loaded from files into surfaces should be cached, since there are only ever hundreds of individual images at most and they are likely to be re-loaded several times. So, surfaces for images loaded from file are cached and only raw pointers to surfaces are stored in objects that use them. The problem occurs when the surfaces are passed to a rendering queue:
void SDL_Renderer::Render(unsigned short depth, const SDL_Rect& position,
SDL_Surface* surface, const SDL_Rect& clip)
{
    render_queue.insert(Queued_Renderable(depth, position, surface, clip));
}

Because the text surfaces aren't cached, there is the possibility that the data pointed to by the SDL_Surface* will be destroyed. So my problem is that I'm not sure what to do. One idea I had is to have a separate render queue for text surfaces that store weak_ptrs to the surfaces. Does this sound like a good idea? Can anyone think of anything better? Perhaps someone can see a flaw in my design? Cheers.

Share this post


Link to post
Share on other sites
Use shared pointers everywhere. They add only minimal overhead that is mostly a per-texture constant, and they also make implementation of the cache easier (since you can turn an actively cached texture into a passively cached texture by turning a strong pointer into a weak pointer).

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Use shared pointers everywhere. They add only minimal overhead that is mostly a per-texture constant, and they also make implementation of the cache easier (since you can turn an actively cached texture into a passively cached texture by turning a strong pointer into a weak pointer).
So you're suggesting I wrap the surfaces that are currently cached in shared_ptrs?

Share this post


Link to post
Share on other sites
Yes.

My caches are usually a pair of maps: one that maps names to shared_ptrs and one that maps names to weak_ptrs. Whenever I get a request for a resource by name, I load it (if it is in neither map), remove it from the weak pointer map (if present) and add it to the strong pointer map (if not present) to mark it as "active". From time to time, I move all pointers from the strong pointer map to the weak pointer map to flush unused elements.

Share this post


Link to post
Share on other sites
Oh, which one would be more efficient:
for(queue_it it = render_queue.begin(); it != render_queue.end(); ++it)
{ // Draw elements in queue in order of depth.
if(it->surface.lock()) Blit_Surface(it->position, it->surface.lock().get(), &it->clip);
}

surface_ptr sfc;
for(queue_it it = render_queue.begin(); it != render_queue.end(); ++it)
{ // Draw elements in queue in order of depth.
if(sfc = it->surface.lock()) Blit_Surface(it->position, sfc.get(), &it->clip);
}


I'm presuming the second...

Share this post


Link to post
Share on other sites
The difference in performance between the two is so small, and the ease of changing from one to another so clean, that merely thinking about the comparison is a waste of time [smile]

In the general case, however, the second is safer (because the weak pointer can't go null between the time you check it and the time you use it).

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
The difference in performance between the two is so small, and the ease of changing from one to another so clean, that merely thinking about the comparison is a waste of time [smile]

In the general case, however, the second is safer (because the weak pointer can't go null between the time you check it and the time you use it).
But presuming there are no callbacks or anything else that could make the weak pointer null, which is faster? I'm not one for premature optimization, although I believe that keeping it in the back of your mind doesn't hurt. In this case though, this function is called every frame, so I wanted to be sure that it's fast.

Quote:
Original post by SiCrane
Wait, are you dumping weak_ptrs into your render queue? Why not just store shared_ptrs in the queue?
Because that way if the situation in my first post happens again, the dialogue box containing the text that was closed won't get rendered. I don't think this will matter considering how quickly the screen will be re-drawn, but there is no harm in doing it this way, right?

Edit: SiCrane, you've got a point that I just realised haha... using shared_ptrs saves checking if the surface is null in the render loop. That would be a plus, but is there another reason you mentioned it?

Another edit: If I use weak_ptrs for the render queue and the lifetime issue occurs, there is a noticable flicker where there is no text on a dialogue, but if I use shared_ptrs, this issue is avoided. So I was wrong about this, it seems:
Quote:
I don't think this will matter considering how quickly the screen will be re-drawn


[Edited by - Mybowlcut on July 21, 2009 9:07:49 AM]

Share this post


Link to post
Share on other sites
Do yourself a favor and write some test code assigning one shared_ptr from another and one shared_ptr to a weak_ptr the back to a shared_ptr and look at how much code get executed in each case. Pay special attention to how many thread synchronized operations there are. shared_ptr to shared_ptr is what happens when you assign a shared_ptr into your queue and it holds shared_ptrs. shared_ptr to weak_ptr to shared_ptr is what happens when you hold weak_ptrs in your render queue. And the second half happens twice if you call lock twice.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Do yourself a favor and write some test code assigning one shared_ptr from another and one shared_ptr to a weak_ptr the back to a shared_ptr and look at how much code get executed in each case. Pay special attention to how many thread synchronized operations there are. shared_ptr to shared_ptr is what happens when you assign a shared_ptr into your queue and it holds shared_ptrs. shared_ptr to weak_ptr to shared_ptr is what happens when you hold weak_ptrs in your render queue. And the second half happens twice if you call lock twice.


I tried to test it out but couldn't step into the boost code for some reason:
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"

int main()
{
boost::shared_ptr<int> source(new int(1));

boost::shared_ptr<int> source_copy(source);

boost::weak_ptr<int> weak_source_copy(source);

boost::shared_ptr<int> another_copy(weak_source_copy.lock());

return 0;
}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this