Skip to main content
gamedev.net
🔒 Locked

Pointer manager issues.

Started by MeshGearFox Apr 5, 2011 at 11:41 PM 3 replies 1.2k views
Original Post
MeshGearFox
MeshGearFox
In my game, it is possible for game objects to acquire references to other game objects. Seeing as it would be useful for them to store said references (instead of re-querying for whatever object they wanted each time they wanted it), I wrote a simple pointer management system to ensure that pointers would be consistent across saves. The way it works is this:

There is a structure which, internally, stores a pointer to the object holding the pointer, a pointer to the object being pointed at, and a string so that the pointer can be easily accessed. These pointer structures are arranged in some broader structure which groups pointers together based on the ID of the holder object.

When a holder object is flagged for deletion, its pointer set is wiped out. Pointers cannot be manually deleted at any other time.

When an object is flagged for deletion, all pointers pointing to it are set to null.

When the game is saved, the IDs of the holder and target pointers are written out. When the game is loaded, the IDs -- which are consistent across a save and load -- are used to rebuild the pointer table.

Pointer tables are local to a level because IDs within a given level are guaranteed to be unique; additionally, pointers to things in other levels is, frankly, something I don't want because it could lead to weird behavior.




For the most part this has worked out fine so far. However, I've recently decided I want a set of game objects that exist externally to any level for various reasons (which are left as an exercise to future generations who might be reading this). I want to allow objects in a given level to point to these extra-level objects, (but not vice versa). I'm encountering a few problems:

1. The pointer null-ing code would work fine on the extra-level objects, in the sense that if a level-object had a pointer to an extra-level object that got flagged for deletion, if the player was in that level, the pointer would get null'd...

2. ... however, the pointer nulling code only runs for the level the player is currently, meaning something in another level with a pointer to an extra-level object flagged for deletion would not have its pointer correctly nulled.

3. Running the pointer nulling code for non-active levels is impractical for two reasons: First, it strikes me as inefficient. Second and more importantly, not every level is loaded into memory at the same time. If an object in a non-loaded level has a pointer to an extra-level object flagged for deletion, its pointers aren't even going to be in memory to get nulled out.

4. Furthermore, if an extra-level object is deleted, its ID will be freed and re-assigned. If a level containing a level-object that had previously stored a pointer to the now-deleted object is then loaded into memory, the loading code will assign the pointer to the WRONG object, since the ID will match, breaking the IDs remaining consistent across saves and loads which had worked out fine when pointers were strictly level-local.

5. In the current system, it should be noted that extra-level objects could safely point to other extra-level objects without these issues afflicting them.

So, a few possible solutions, none of which I like:

1. IDs are unique, but released for re-assignment when their associated object is deleted. This works fine for level-objects. As mentioned in problem 4, it causes problems with extra-level objects. An incorrect way to address this problem would be to never re-use IDs. A better solution would be to not re-use IDs for extra-level objects until it is determined that doing so would be safe; however I am not sure how to go about that.

I think something that might be viable is this: Flag a released ID as being unusable upon its release. When saving the game, test all

2. In terms of the other problems, I have considered having objects store pointers to the pointers that point at them, but this sounds needlessly obtuse, and still doesn't address the issue of nullifying pointers that aren't in memory.

3. Seeing as non-existent-but-potentially-existent can't really be nulled, one possible solution would be to store a list of target IDs in the extra-level object structure. When an extra-level object got flagged for deletion, it would add its ID to this list, so that, on accessing it, whatever object was accessing it would have its pointer figure out that it should null itself.

4. Implement a proper garbage collector of some sort for extra-level objects and just keep objects around until nothing pointed at them, which would probably address all of my issues, but would be inconsistent with how things are handled elsewhere and might be overkill. I'm looking more at this, though, really. Garbage collection would probably occur when saving the game, and probably wouldn't be a huge deal since it wouldn't really be necessary to garbage collect regular level objects.

return0
return0
Delete your code. Use a GC'd language. Implement what you need.
MeshGearFox
MeshGearFox
Garbage collection isn't the issue. Maintaining reference consistency across loading/saving is.
Hodgman
Hodgman
Delete your code. Use a GC'd language. Implement what you need.
Wow. Read the problem statement, then troll.
I raed 'pointer' an post teh 'GC' HUR HUR!!
No.
4. Furthermore, if an extra-level object is deleted, its ID will be freed and re-assigned. If a level containing a level-object that had previously stored a pointer to the now-deleted object is then loaded into memory, the loading code will assign the pointer to the WRONG object, since the ID will match, breaking the IDs remaining consistent across saves and loads which had worked out fine when pointers were strictly level-local.
Do you have to reassign IDs? Assuming 32-bit IDs, will you have more than 4 billion extra-level objects over the lifetime of a save game?
Alternatively, solutions for the ABA problem might be applicable here. One is to reserve some bits of the ID as the actual ID value, and some bits as an instance counter.
For example, with a 32-bit ID, you can use the top 8 bits as a counter of how many times the ID has been recycled, and the lower 24 bits as the ID. When looking up an object by ID, you mask out the top 8 bits, so only the ID part is used during the look-up, but then once you find an object with that ID, you compare it's entire ID field with the requested ID field.Object* Find( u32 id )
{
u32 actualID = id & 0x00FFFFFF;
Object* found = map[actualID];
if( found && found->id == id )//make sure the ID hasn't been recycled
return found;
return 0;
}
This is a work-around, because it can still fail in the case where the counter field overflows - in this example, if an ID is obtained, then recycled exactly 256 times, then looked-up, it will pass the ABA test even though it's been recycled. To mitigate this, you can allocate more bits to the counter field and less to the ID field.
MeshGearFox
MeshGearFox
Not sure if this would be more or less hackish, but I'm thinking of doing something with timestamps:

1. Object A points at Object B.

2. The player leaves whichever level Object A was in, meaning Object A's pointer checking code is no longer being run.

3. The player saves and reloads the game, wiping out the memory and forcing the pointer tables to be rebuilt.

4. Object B is deleted and a new object is created, absorbing the same ID.

5. The player goes back to the level A was in, causing that level to load. The pointer table attempts to rebuild.

6. The pointer table rebuild function finds an object with the same ID as object B; however, when object B was created, it had some timestamp stored on it. When the pointer table was written out, the info on the expected timestamp was also stored.

7. The pointer table rebuild function sees that the timestamps don't match, and correctly nulls out object A's pointer.

This still doesn't fix everything but it seems like it might work.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.