Dynamic memory in game

Started by
13 comments, last by rip-off 12 years, 5 months ago
I find boost::multi_array to be horrendously ugly to use for the most common case of a 2D array. I'd rather use a simple wrapper around std::vector instead:

template<class T>
class array2d
{
public:
array2d(unsigned w, unsigned h)
:
storage(w * h),
width_(w)
{
}

unsigned width() const { return width_; }
unsigned height() const { return storage.size() / width_; }

T &operator()(unsigned x, unsigned y)
{
return storage[index(x,y)];
}

const T &operator()(unsigned x, unsigned y) const
{
return storage[index(x,y)]);
}

private:
std::vector<T> storage;
unsigned width_;

unsigned index(unsigned x, unsigned y)
{
return (y * width_) + x;
}
};

Boost's multi array would be great for the general case, but I see it as being almost as nonsensical to use it for a 2D array as to use it for a 1D array.
Advertisement

Second is that I keep a "scratch buffer" hanging around for short-lived run-time needs (generally within the scope of a single function). This is allocated one-time-only at startup, and about 2 MB is a decent enough size (your needs may be bigger or smaller). Instead of needing to make a new allocation I just set a pointer to this buffer and work away.
[/quote]
Do you have any control access to this, to ensure that a function using a portion of the scratch buffer won't get corrupted by calling another function which decides to use it?

Or is it only used when interacting with external API's that require such space?
@mhagain Do you have any control access to this, to ensure that a function using a portion of the scratch buffer won't get corrupted by calling another function which decides to use it?

Or is it only used when interacting with external API's that require such space?
Not answering for mhagain, but I'll butt in with my experiences tongue.gif
When I worked on PS2, we put almost everything into stack based scratch buffers (mark&release / stack allocators) -- these kinds of allocators outperform almost every other option (allocation is basically just a "+=").
To manage them, allocations were added to a stack, and the only way to release anything was to clear the entire stack. As primitive as it sounds, once you start using such a system, it can be quite easy and powerful.
We had several different stacks with different lifetimes, e.g. there might be one with no guarantees (use/clear as a static scratch space), one for per-frame temporaries, and others associated to geographical areas of the game (which would be cleared/initialized by the world-streaming system).

In my current engine, I use something similar to Dice's scope-stacks to manage all engine allocations (global new/malloc type allocations are banned - you must specify from where you are obtaining your memory); the concepts presented by Dice allow you to reconcile such a simple allocator with C++ features (constructors/destructors) and manage lifetimes in a sensible and predictable way (with much more flexibility than the above ps2 anecdote).

If you need some scratch-space in a function, you can create a local "allocation scope", and allocate (quick "+=") whatever data you want from it. When the function returns, the local "allocation scope" variable goes out of scope and unwinds the stack-allocator back to where it was before the function began (and if necessary, calls any required destructors).

Having used this allocation strategy for a while, I feel the same joy as I did when first discovering RAII for the first time -- it's an amazingly simple idea with major power that greatly simplifies my code and reduces memory related bugs.

Unfortunately the gameplay code (i.e. the Lua VM) still makes heavy use of a general purpose allocator like malloc (thousands of dynamic allocations per frame), though it's still not really a bottleneck.
Again sorry Im short of time lately....

Of course once deleted you have to do "collision = NULL" so that it know it points to nothing.

Thanks a lot, there is no more bug in my code. Adding "Collision::Obj().collision = NULL;" after deleting memory solved the problem. And yea the variable collision is declared in class in header file(collision.h). I am doing the memory allocation in collision.cpp but delete it out of the class in another source file(not collision.cpp) and its not static. But you said that such using of the variable looks to you dangerous, are there any reasons for worry if I'm doing it like this?

Thanks also for everyone who posted. I'm wondering if the boost multiarray is worth using? Implementing next library just for arrays? Is it really popular and worth among game programming?

Edit: Aww... I have just cheked again if the problem really disappeared but it seems that it just happens now more rarely. But its better than was at least:
for(int i = 0; i < size; i++)
{
delete [] Collision::Obj().collision; //here debug detects the eroor
}
delete [] Collision::Obj().collision;
Collision::Obj().collision = NULL;

You are deleting the data more often than you think. The delete and delete[] expressions performs checks to ensure that trying to deallocate NULL is a no-op. If nulling the pointers reduces the frequency of errors, then what has been happening is that you have been leaving dangling pointers.


I am doing the memory allocation in collision.cpp but delete it out of the class in another source file(not collision.cpp) and its not static.
[/quote]
In C++, when dealing with raw pointers one must deal with the notions of "lifetime" and "ownership". Generally, some object or subsystem is said to "own" the pointer, and is responsible for deallocating them. They may be responsible for allocating them, or the ownership might be transferred in some cases. Ownership transfer is generally one way, once the new owner is established the old owner relinquishes all claims to the memory (i.e. holds no pointers to it).

Sharing pointers is tricky, and often best handled by using classes such as std::shared_ptr<> to manage the lifetime of the object.

The question is, what does this array represent, what is the appropriate lifetime and why are there two subsystems apparently fighting over ownership? What is Collision? A namespace? A class? What is Obj()?

I guess it relates to map data, and the map is tied to a particular level. When you're moving between levels, you need to ensure you're not relying on any stale data, particularly stale pointers, pointing to things from the previous level.

One way to do this is to use RAII to tie the owernship and lifetime of the various objects together. E.g. a Level object might have a Map as a member, and the Map might contain this collision data. When you discard the Level object, all these sub-objects are thrown away together and there should be no dangling references left around. Then you instantiate a new Level.


But you said that such using of the variable looks to you dangerous, are there any reasons for worry if I'm doing it like this?
[/quote]
You're passing around raw pointers to different objects/subsystems, and deallocating them. What is the other object/subsystem expecting will happen?

Instead of using pointers, wrap the pointers in objects (such as the array2d or boost::multi_array, or a higher-level one again). Pass this object by reference to areas that need it, or invert the control such that you don't have to extract the guts of the object in order to interact with it.

This topic is closed to new replies.

Advertisement