Contents
Managing Data Relationships
 
 
Printer-Friendly VersionPrinter-Friendly Version
 


Part of:



[More information...]
 

Latest News
spacer View All spacer
 
November 22, 2009
 
Video Game Watchdog National Institute On Media And The Family Shutting Down [12]
 
Modern Warfare 2 Infinity Ward's 'Most Successful PC Version' Yet [15]
 
New Tech, Design Details Of Project Natal To Emerge At Gamefest In February
spacer
Latest Jobs
spacer View All     Post a Job     RSS spacer
 
November 22, 2009
 
Trion Redwood City
Sr. Environment Artist
 
Trion Redwood City
Sr. Evnironment Modeler
 
Sucker Punch Productions
Network Programmer
 
Sucker Punch Productions
Texture Artist
 
Sucker Punch Productions
Character Artist
 
Sucker Punch Productions
3D Environment Artist
 
Crystal Dynamics
Sr. Level Designer
 
Sony Online Entertainment
Brand Manager
spacer
Latest Features
spacer View All spacer
 
November 22, 2009
 
arrow Upping The Craft: Susan O'Connor On Games Writing [7]
 
arrow Small Developers: Minimizing Risks in Large Productions - Part II [7]
 
arrow iPhone Piracy: The Inside Story [51]
 
arrow And Yet It Grows: Analyzing the Size and Growth of the European Game Market [5]
 
arrow NPD: Behind the Numbers, October 2009 [13]
 
arrow Reflecting On Uncharted 2: How They Did It [5]
 
arrow Sponsored Feature: Rasterization on Larrabee -- Adaptive Rasterization Helps Boost Efficiency
 
arrow Postmortem: Wadjet Eye's The Blackwell Convergence [2]
spacer
Latest Blogs
spacer View All     Post     RSS spacer
 
November 22, 2009
 
Managing Creativity
 
Time Fcuk - A Postmortem [3]
 
Accepting the Inherent Value of Games [1]
spacer
About
spacer News Director:
Leigh Alexander
Features Director:
Christian Nutt
Editor At Large:
Chris Remo
Advertising:
John 'Malik' Watson
Recruitment/Education:
Gina Gross
 
Features
  Managing Data Relationships
by Noel Llopis
1 comments
Share RSS
 
 
June 25, 2009 Article Start Previous Page 2 of 3 Next
 

On the positive side, now that we've added an indirection (index to pointer, pointer to data), we could relocate the data, update the pointer in the array, and all the indices would still be valid. We could even delete the data and null the pointer out to indicate it is gone. Unfortunately, what we can't do is reuse a slot in the array since we don't know if there's any data out there using that particular index still referring to the old data.

Because of these drawbacks, indices into an array of pointers is usually not an effective way to keep references to data. It's usually better to stick with indices into an array of data, or extend the idea a bit further into a handle system, which is much safer and more versatile.

Handle-ing the Problem

Handles are small units of data (32 bits typically) that uniquely identify some other part of data. Unlike pointers, however, handles can be safely serialized and remain valid after they're restored. They also have the advantages of being updatable to refer to data that has been relocated or deleted, and being possible implement with minimal performance overhead.

The handle is used as a key into a handle manager, which associates handles with their data. The simplest possible implementation of a handle manager is a list of handle-pointer pairs and every lookup simply traverses the list looking for the handle. This would work but it's clearly very inefficient. Even sorting the handles and doing a binary search is slow and we can do much better than that.

An efficient implementation of a handle manager is available online at www.gdmag.com/resources/code.htm. The handle manager is implemented as an array of pointers, and handles are indices into that array. However, to get around the drawbacks of plain indices, handles are enhanced in a couple of ways.

Listing 1: The Handle Structure

struct Handle
{
Handle() : m_index(0), m_counter(0), m_type(0)
{}

Handle(uint32 index, uint32 counter, uint32 type)
: m_index(index), m_counter(counter), m_type(type)
{}

inline operator uint32() const;

uint32 m_index : 12;
uint32 m_counter : 15;
uint32 m_type : 5;
};

Handle::operator uint32() const
{
return m_type < < 27 | m_counter < < 12 | m_index;
}

In order to make handles more useful than pointers, we're going to use up different bits for different purposes (see Listing 1). We have a full 32 bits to play with, so this is how we're going to carve them out (see Figure 1):


Figure 1: 32 bit handle.

The index field. These bits will make up the actual index into the handle manager, so going from a handle to the pointer is a very fast operation. We should make this field as large as we need to, depending on how many handles we plan on having active at once. 14 bits give us over 16,000 handles, which seems plenty for most applications. But if you really need more, you can always use up a couple more bits and get up to 65,000 handles.

The counter field. This is the key to making this type of handle implementation work. We want to make sure we can delete handles and reuse their indices when we need to. But if some part of the game is holding on to a handle that gets deleted -- and eventually that slot gets reused with a new handle -- how can we detect that the old handle is invalid?

The counter field is the answer. This field contains a number that goes up every time the index slot is reused. Whenever the handle manager tries to convert a handle into a pointer, it first checks that the counter field matches with the stored entry. Otherwise, it knows the handle is expired and returns null.

The type field. This field indicates what type of data the pointer is pointing to. There are usually not that many different data types in the same handle manager, so 6-8 bits are usually enough. If you're storing homogeneous data, or all your data inherits from a common base class, then you might not need a type field at all.

 
Article Start Previous Page 2 of 3 Next
 
Comments

Thomas Göttlich
profile image
The handle based system seems to be very handy if I want to relocate data.
In order to not having to check for null, the handle manager could return a reference to some dummy data, e.g. if an invalid texture is requested, a small black dummy texture could be returned.

I have same question regarding the usage of the data though:

How would you handle the rendering of a mesh for example?

Suppose we have the mesh, the shaders and some textures for rendering.
All is referenced by a handle, so when rendering we'd have to perform the following steps:
1. retrieve a reference to the handle manager(s)
2. retrieve the pointer to the data from the manager
- decode the type
- check the handle's counter
- index into the array and return the pointer
3. bind the resource for rendering

When using a smart pointer, you'd need the following steps:
1. get the indirection structure pointed to by the smart pointer
2. retrieve the pointer to the data from the indirection structure
3. bind the resource

So, which method would you suggest if serialization is not considered?


none
 
Comment:
 


Submit Comment