🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Game Engine API, smart pointers or not?

Started by
35 comments, last by frob 5 years, 2 months ago

Hi,

 

I'm writing a game engine, when my API returns a pointer, most of the time it is meant to be used and then discarded right away. But I have some things where it might be useful to store the pointer.

E.g. addNewGameObjectToScene. It would create a new object, add it to the scene, and return a pointer. But there is one obvious problem, there is no way to check if the returned pointer is valid. Because the newly created object could be deleted at any point, and then the pointer is invalid and using it would cause crashes. Therefore, to me it would make sense to use smart_pointers to fix that. But have found that not everyone agree's with me on that.(And I personally don't like to use smart pointers in api if I can avoid it)

So what do you guys think? If I should use raw pointers, how would I fix the issue, and if I should use smart pointers, will it impact performance or drive people away from using my api?

 

Thanks!

Advertisement

Ha! You're here from Reddit I see :D So you know mostly what I will say.... One thing though........ How can " the newly created object could be deleted at any point".  The only way this can happen is because of other threads and if that's the case you could have some race conditions which is another whole can of worms. Now I see why you want to use weak pointers.  But first, am I understanding you correctly in that other threads can delete objects you have a pointer to?
 

You could use handles. Instead or refering to objects directly with pointers, you could have a Unique id attached to the game object and then obtain the pointer (everytime when needed and don't cache it anywhere) using that handle.
A lot of people are doing this.

If you want the pointer approach shared_ptr+weak_ptr could do the thing for you. And if needed you could still keep the raw-pointer version when you don't need to cache the object, in order to reduce ref-counting (if retuning by share_ptr<T>& won't work).

My personal preference, be it C, C++, Java, .NET, whatever, is to have functions either return unique ownership (normally a new object, which is then the sole responsibility of the caller), or to return a reference/pointer to something the API still owns and manages. In C++ `unique_ptr` and raw pointers fulfil that nicely.

For function parameters, the same deal where possible, the API either takes ownership, or just uses the reference for a fixed duration, normally the function call itself.

2 hours ago, Tim Leijten said:

E.g. addNewGameObjectToScene. It would create a new object, add it to the scene, and return a pointer. But there is one obvious problem, there is no way to check if the returned pointer is valid. Because the newly created object could be deleted at any point, and then the pointer is invalid and using it would cause crashes. Therefore, to me it would make sense to use smart_pointers to fix that.

Sounds more like a `create`. I think the problem here is the "deleted at any point". What is able to delete it, why is it? Are you multi-threading the game logic? You definitely don't want an API design where a pointer/reference/etc. gets invalidated by some other thread, maybe before a create/get function even returns.

A solution I used with a couple of things was to say my object references must stay valid during a particular update frame (I was essentially storing them in std::vector). This meant  any pointer within an update was guaranteed safe, but I had to use unique ID's to track specific things between frames.

 

1 hour ago, ongamex92 said:

You could use handles. ... If you want the pointer approach shared_ptr+weak_ptr could do the thing for you.

 

I think it is important though, even if you do use "shared_ptr" or "handle", or even in languages like Java and .NET, to try and stick to a single ownership model for object lifetime where possible, as it is easier to think through, and even garbage collector languages have things it does poorly (Close/Dispose/etc.) or the possibility of memory leaks (e.g. forgetting to remove things from collections).

e.g. in this case you object is probably still "owned" by the scene, and once removed it is marked "dead", removed from the scene collections, any getter fr it returns null, it's not rendered, no physics, no updates, etc. and any remaining references are mostly just a safe way for other objects to see it "is dead" in a safe way.

 

2 hours ago, Tim Leijten said:

if I should use smart pointers, will it impact performance or drive people away from using my api?

`unique_ptr` has literally zero cost, `shared_ptr` you probably want `enable_shared_from_this` (or traditional intrusive ref counting). You probably regardless want to avoid using heap dynamic allocations for objects. The fastest is generally going to be a vector<T> of values, due to continuous memory, but sometimes that is not practical.

2 hours ago, Gnollrunner said:

Ha! You're here from Reddit I see :D So you know mostly what I will say.... One thing though........ How can " the newly created object could be deleted at any point".  The only way this can happen is because of other threads and if that's the case you could have some race conditions which is another whole can of worms. Now I see why you want to use weak pointers.  But first, am I understanding you correctly in that other threads can delete objects you have a pointer to?
 

No. I don't use threads.

If obj A creates obj B and stores a pointer to it.(E.g. to get the position or communicate something else). The because of some player action, obj B is deleted. Now obj A has an invalid pointer. 

So obviously I want there to be a way for A to check if B exists. E.g. a weak_ptr. Anothrr solution would be a messaging system or a table containing all valid GameObjects(and then check if the pointer is in there), but that's waayyy more work than using a weak_ptr.

2 hours ago, ongamex92 said:

You could use handles. Instead or refering to objects directly with pointers, you could have a Unique id attached to the game object and then obtain the pointer (everytime when needed and don't cache it anywhere) using that handle.
A lot of people are doing this.

If you want the pointer approach shared_ptr+weak_ptr could do the thing for you. And if needed you could still keep the raw-pointer version when you don't need to cache the object, in order to reduce ref-counting (if retuning by share_ptr<T>& won't work).

I'm not a fan of handles as it doesn't play well with oop.

 

I would prefer going the pointer route. So you are saying shared and weak ptr's are ok when needed. And I should use raw ptr when possible?

1 hour ago, SyncViews said:

Sounds more like a `create`. I think the problem here is the "deleted at any point". What is able to delete it, why is it?

E.g. something a player does causes the object to delete itself. Why does everyone think it has something to do with threading?

1 hour ago, SyncViews said:

solution I used with a couple of things was to say my object references must stay valid during a particular update frame (I was essentially storing them in std::vector). This meant  any pointer within an update was guaranteed safe, but I had to use unique ID's to track specific things between frames.

It's posible, but I don't think it's needed because in general, the pointer never changes unless you delete it. But I'll keep it in mind.

1 hour ago, SyncViews said:

I think it is important though, even if you do use "shared_ptr" or "handle", or even in languages like Java and .NET, to try and stick to a single ownership model for object lifetime where possible,

I agree it's best to have one owner, and it would work for me I think, but there is unfortunately no way to check if a pointer is valid. Making it a bit harder to do.

 

Handles aren't a bad idea, I just don't like to have my game code cluttered with handles and getPointerByHandle's. And even worse would be switching the entire engine to handles.(I wouldn't have access to classes)

 

So I think I'm just going to take the smart pointer route where needed. Thanks for all your help!

2 minutes ago, Tim Leijten said:

No. I don't use threads.

If obj A creates obj B and stores a pointer to it.(E.g. to get the position or communicate something else). The because of some player action, obj B is deleted. Now obj A has an invalid pointer.

Yes, so this is what weak pointers are used for.  I just was not clear if this level of control was part of your API. You can in fact use a weak pointer in this case. That being said as I mentioned in reddit,  the standard library smart pointer implementation has a lot of overhead, so there are trade-offs.  But if you don't want to do your own reference counting system this might be the best choice for what you want to do.

2 minutes ago, Gnollrunner said:

Yes, so this is what weak pointers are used for.  I just was not clear if this level of control was part of your API. You can in fact use a weak pointer in this case. That being said as I mentioned in reddit,  the standard library smart pointer implementation has a lot of overhead, so there are trade-offs.  But if you don't want to do your own reference counting system this might be the best choice for what you want to do.

Thanks for your reply! I agree weak_ptr is the best choice. Just wasn't sure.

This is my first game engine and I don't have much experience with C++ yet, so I don't want to make it too hard for myself. I might do my own reference counting at some point, but not yet.(I don't even have an idea how I would do that.)

7 minutes ago, Tim Leijten said:

E.g. something a player does causes the object to delete itself. Why does everyone think it has something to do with threading?

It wasn't clear to us that you were going to save the pointer, go off and do something else and then come back later and expect it to be intact.  So now it's a little more clear.

1 hour ago, Gnollrunner said:

It wasn't clear to us that you were going to save the pointer, go off and do something else and then come back later and expect it to be intact.  So now it's a little more clear.

No no. I wasn't trying to be mean. It's just something I notice in general, both here and on reddit, people expect that if something changes it must be another thread. Which I find interesting.

This topic is closed to new replies.

Advertisement