Type-safe GUID handles and data hiding

Started by
26 comments, last by swiftcoder 16 years, 9 months ago
In my current renderer design I am employing the use of GUIDs to hide the actual (API) implementation of different resources, e.g: typedef unsigned int GUID_Type; GUID_Type myTexture; renderer->createTexture ( &myTexture, creationParams ); My recent qualm with this is that I could do something like this: renderer->bindTexture( myShaderHandle ); Clearly my design isn't type-safe, and could result in run-time silliness. So I thought I would wrap each type of resource GUID in a different class to gain typesafety:
class TextureHandle {
public:
    GUID_Type id;
};
Now class instances are passed to the renderer, any attempt to muddle different types of handles will result in a nice big compile-time error. But now I'm wondering about data hiding, the client could easily modify the id of a resource and once again introduce a run-time bug. So, if I'm concerned with data hiding the requirements become: The user needs read-only access to the id, But the renderer needs read/write access to the id. I could hide id behind getters and setters but that doesnt really get me anywhere does it, and everybody knows getters and setters are bad [wink]. Another possibility is making the renderer a friend of each resource handle class. The problem with this is that the renderer is an abstract class, I could make the base class a friend for each resource handle but that wont help the derived implementations of renderer without them jumping through (overhead adding) hoops to access the handles' GUIDs. Any ideas? Perhaps data-hiding isnt worth it in this case, if the client wants to mangle the id then thats their problem? I'd be intrigued to hear peoples oppinions about this or alternate solutions.
Advertisement
The biggest issue is your requirement that the renderer can alter IDs.

If that weren't an issue, you might be able to get away with having an inner class that wraps around the ID. The issue becomes keeping the inner class and outter class in synch when the renderer changes the ID.

Probably not the best imlpementation ... but it could work.
#include <iostream>typedef unsigned int GUID_Type;class TextureHandler {public:	class Reader {	friend TextureHandler;	public:		const GUID_Type getID() const;	private:		void setID(GUID_Type id);		GUID_Type id;	};	TextureHandler(GUID_Type id);	const Reader &getReader() const;	void setID(GUID_Type id);	GUID_Type getID();private:	Reader r;};typedef const TextureHandler::Reader& PublicTextureHandler;const GUID_Type TextureHandler::Reader::getID() const {	return id;}void TextureHandler::Reader::setID(GUID_Type id) {	this->id = id;}TextureHandler::TextureHandler(GUID_Type id) {	r.setID(id);}PublicTextureHandler TextureHandler::getReader() const {	return r;}void TextureHandler::setID(GUID_Type id) {	r.setID(id);}GUID_Type TextureHandler::getID() {	return r.getID();}int main(int argc, char **argv) {	TextureHandler handle(5);	PublicTextureHandler pub_handle = handle.getReader();	std::cout << pub_handle.getID() << std::endl;	handle.setID(6);	std::cout << handle.getID() << std::endl;	std::cout << pub_handle.getID() << std::endl;}


Pardon my bad C++ -- I haven't touched it for a year, so my consts and references might have some holes -- especially when it comes to pointers. boost:smart_pointer might be a better bet for you -- but I don't know what tools you have at your disposal. Either way, this seems pretty hackish to me.
I just tested a release build using a pointer to the TextureHandler, setting a value, getting a PublicTextureHandler, and deleting the TextureHandler. I expected an access error. Reading from the PublicTextureHandler gives a value of 0. I don't know if this is defined behavior or not, maybe someone else can chime in. It seems rather odd.

Either way, you could just pass by value instead of reference and solve the issue.
Ok well I was looking over your code, trying to see how it applies.
Does TextureHandler translate to my renderer? And PublicTextureHandler is equivalent to my TextureHandle?
If so then that is somewhat similar to one of ideas I proposed in my top post, which I do feel is a little hackish.

Quote:The biggest issue is your requirement that the renderer can alter IDs.

Yes exactly, the renderer does of course need to be able to initalise them so that the client can use the handles to access per-resource services later on.
I equally would like handles to be re-usable, so that a single handle instance can be used to create-bind-destroy a resourse and then start all over again if need be.

As far as your trial run and deleting the TextureHandler instance goes, that sounds like undefined behaviour to me, but you're unlikely to get any compiler warnings and you can easily be lucky enough not to get any access error.
Why do you actually need an unsigned integer as a handle? Why can't you not just use a typed pointer? Wouldn't that solve all your problems?
A typed pointer to what?
The underlying instance could be anything.

As far as i'm aware there are two main method to achieve what i'm doing:

1) Handles (the handle/body idiom? maybe?)

2) Polymorphism
i.e.

class TextureResource { };class TextureResource_D3D : public TextureResource { };class TextureResource_OGL : public TextureResource { };// Use:TextureResource* myTexture;renderer->createTexture( myTexture, creationParams );

The polymorphism approach is more typesafe but incurs overheads that handles can avoid.

Either way the client needs some way to interact with the implementation specific services like binding and destroying a resource. In the polymorphism approach this interaction is done through the TextureResource base class interface. In the handle approach this is all done through the render-device interface (much like the underlying API anyway).
I had a quick look at the Handle/Body idiom (google). Still have to look at the bridge patter (fortunately I have the GoF book).

For this case (supporting OGL and D3D; which is a major task) I would use polymorphism. I don't see what overhead that should cause over handles.

When you use polymorphism you can use pointers instead of integer GUID. This is type safe and also ensures uniqueness which you would have to ensure yourself if you used integers.
If you usd integers GUIDs you wouldn't know the type of the resource and you had to look it up somehow plus you need some way to resolve this id to the actual instance of the class (probably with a hash table -> THIS is slow compared to the polymorphism approach)
The way I was thinking about it is that with GUIDs the render-device is entirely responsible for all GPU resources and not its client.
But a hash-map is certainly not necessary.

Off the top of my head...

Using GUIDS
  • cheap to pass around
  • contents are easily and safely examined - useful for state sorting for example
  • type-safety can be achieved by wrapping in a class
  • contents are too easily dangerously modified
  • (re)serialisable
  • All API specific code is kept in one place/class
  • render-device owns all GPU resources
    • during a device-lost state the render-device can release and re-load all resources and the client need never know or care
    • all services are performed through the render-device (potential for optimisations)
    • most of the costly virtual methods calls are made only at initialisation and kept to a minimum at render-time.
    • Since the render-device is the only polymorphic class, if the platform can be known at compile-time (when targetting a specific platform for example) then virtual functions can be inlined (circumstances permitting) or atleast cost no more than a non-virtual call.
    • Potential for changing API on the fly! (same argument as during device-lost state)
    • Under OGL
      • GPU resources from the API are integers handles anyway and can be trivially cast directly into unsigned int handles

    • Under D3D
      • GPU resources from the API are class instances, the unsigned int handle is used for a simple array lookup - alternatively the device could cast a D3D-resource pointer into a handle-type (perhaps a handle should be an unsigned long in this case)

Using polymorphism
  • pointers are cheap to pass around
  • automatically typesafe
  • must not be tempted to delete a pointer
  • can be dangerously modified
  • harder to safely examine the address of a pointer - e.g. for state sorting
  • cannot be serialised
  • API specific code is distributed to many classes
  • expensive virtual calls during rendering usually need to be made all over the place
  • each resource needs to own a reference to its render-device - memory overhead, possibly even one virtual call to the resource makes another virtual call to the render-device.
  • client effectively 'owns' the resources not the render-device (though the device can still keep a local copy)
    • During a device-lost state the client must re-request all their resources - all resource-owners need to be notified
    • No chance of the render-device optimising resource use since it doesnt own them.
    • changing API on the fly is difficult at best
    • little chance of compile-time optimisations.


So I still think the handles are more efficient and actually easier to manage than a pointer since its ignorant to whatever the render-device decides to do with the underlying resources.

Edit: My original question is still open by the way, I'd still be intrigued to hear oppinions, though I am tempted to just use public integers wrapped in a type-safe class.
Well, you have put quite a bit of thought into this. I do not agree to everything you claim but it doesn't matter.
As for wrapping the integer GUIDS into a class for type safety: I wouldn't do it! It smells like a horrible hack and it is the fault of the client if it passes the wrong numbers into the functions.

Edit: device-lost state -> This can only happen in DX9 right?

This topic is closed to new replies.

Advertisement