• Advertisement
Sign in to follow this  

Unity Unique ID per type

This topic is 757 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,
 
I have a Scene graph of SceneNodes. Every SceneNode has a vector of Component*.
 
Component* base class of all types of components in my scene, like a CameraComponent, LightComponent, CharacterController, etc. (Like Unity.)
 
 
I am working serializing the data so I can store in something like an XML or JSON file.
 
The only issue I have right now is creating a unique ID for every type of component that is cross platform and is the same on every run.
 
I know there is the hacky way of having a big enum, in which you create an ID for every class you create. However  I don't like this at all. Is there a better way to do this, where it generates id's automatically?
 
Thanks in advance!
 
 
 
EDIT:
 
I think I have found a way :D
I simply generate a hash based on the class name. I can also replace the RTTI with a static function for every type if I really want to. (however I don't really see a point ATM.)
It also allows me to change to a different hashing algorithm, should this one fail me.
 
struct TypeHash
{
  //check if types are equal
  friend inline bool operator==(const TypeHash& l, const TypeHash& r)
  { return l.id == r.id; }
  //check if types are different
  friend inline bool operator!=(const TypeHash& l, const TypeHash& r)
  { return !operator==(l, r); }


  //for sorting
  friend inline bool operator<(const TypeHash& l, const TypeHash& r)
  { return l.id < r.id; }


  template<typename T> friend TypeHash HashType();
private:
  TypeHash(uint64_t id) : id(id) {}
  uint64_t id;
};


//create a hash based of a type name
inline uint64_t HashTypeName(const char* str)
{
  //djb2 algorithm from here:
  //http://www.cse.yorku.ca/~oz/hash.html
  uint64_t hash = 5381;
  int c;
  while((c = *str++) != 0)
    hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
  return hash;
}


template<typename T> inline TypeHash HashType()
{
  //a way of generating a hash that is cross platform
  static TypeHash hash = HashTypeName(typeid(T).raw_name());
  return hash;
}
 

Share this post


Link to post
Share on other sites
Advertisement

That should work (except there is no such thing as raw_name in std::type_info, it's just name).

 

A few notes:

  • As it stands, your approach will generally "work fine" but the program is ill-formed because you don't include <typeinfo> prior to evaluating a typeid expression (your compiler should warn about that?).
  • You are caching the hash in a static variable. Keep it that way. The temptation exists to instead make the hash function constexpr. Don't do that because it will not work on the stricter compilers as the name is not a constant expression (it sure is, how could it possibly not be... but the standard and the compiler think differently) and will be vastly inferior on the more forgiving compilers (which will compile fine, but evaluate at runtime).
  • std::type_info has a member function hash_code which just looks like what you want, only better, faster, and standard. Yeah right. Don't fall for that. The hash provided by the standard not only isn't portable (that would be quite hard to do, admittedly), but it does not even guarantee that the value stays the same between different runs of the same executable. Which, frankly, is total shit.
  • The standard also provides std::type_index, which suggests by its name and its description that it could be very useful (index? as in unique number? great!), but it is really just a wrapper around std::type_info which adds operators <= and >= in terms of std::type_info.before(). Other than for using it as key in unordered standard containers, it's pretty useless.
  • Instead of using std::type_info.name(), you could use the well-known hack with a helper class that contains a static function which evaluates some flavour of __func__ or __PRETTY_FUNCTION__ or whatever you want to use, optionally adding an offset into the string constant to strip off the ugly mangling. These string constants are not constexpr either (although I think they should be, unless the name of a type can change during a program's execution which would be a big WTF, they are pretty darn constant expressions), but it is less bloat than using std::type_info (especially with GCC which has a very poor implementation), and you save yourself from including another header. I seem to remember someone even posted a complete, usable implementation of the __func__ hack on this site not long ago.
  • From the most pedantic point of view, using the __func__ hack even makes your program a little more robust. The typeid operator does not guarantee that the same type_info object is returned for different invocations in the same program with the same type. This sounds like something you could take for granted, and this is probably what happens anyway, but in the strictest sense, that's not the case. The standard merely says that some type_info (or derived) object with static storage duration is returned (and leaving unspecified whether destructors are called), and that the objects from different typeid expressions with the same type compare equal. That doesn't mean that they are equal, or that that name() returns the same value (or even the same pointer).

Share this post


Link to post
Share on other sites


 

That should work (except there is no such thing as raw_name in std::type_info, it's just name).

 

A few notes:

  •  
  • As it stands, your approach will generally "work fine" but the program is ill-formed because you don't include <typeinfo> prior to evaluating a typeid expression (your compiler should warn about that?).
  •  
  • You are caching the hash in a static variable. Keep it that way. The temptation exists to instead make the hash function constexpr. Don't do that because it will not work on the stricter compilers as the name is not a constant expression (it sure is, how could it possibly not be... but the standard and the compiler think differently) and will be vastly inferior on the more forgiving compilers (which will compile fine, but evaluate at runtime).
  •  
  • std::type_info has a member function hash_code which just looks like what you want, only better, faster, and standard. Yeah right. Don't fall for that. The hash provided by the standard not only isn't portable (that would be quite hard to do, admittedly), but it does not even guarantee that the value stays the same between different runs of the same executable. Which, frankly, is total shit.
  •  
  • The standard also provides std::type_index, which suggests by its name and its description that it could be very useful (index? as in unique number? great!), but it is really just a wrapper around std::type_info which adds operators <= and >= in terms of std::type_info.before(). Other than for using it as key in unordered standard containers, it's pretty useless.
  •  
  • Instead of using std::type_info.name(), you could use the well-known hack with a helper class that contains a static function which evaluates some flavour of __func__ or __PRETTY_FUNCTION__ or whatever you want to use, optionally adding an offset into the string constant to strip off the ugly mangling. These string constants are not constexpr either (although I think they should be, unless the name of a type can change during a program's execution which would be a big WTF, they are pretty darn constant expressions), but it is less bloat than using std::type_info (especially with GCC which has a very poor implementation), and you save yourself from including another header. I seem to remember someone even posted a complete, usable implementation of the __func__ hack on this site not long ago.
  •  
  • From the most pedantic point of view, using the __func__ hack even makes your program a little more robust. The typeid operator does not guarantee that the same type_info object is returned for different invocations in the same program with the same type. This sounds like something you could take for granted, and this is probably what happens anyway, but in the strictest sense, that's not the case. The standard merely says that some type_info (or derived) object with static storage duration is returned (and leaving unspecified whether destructors are called), and that the objects from different typeid expressions with the same type compare equal. That doesn't mean that they are equal, or that that name() returns the same value (or even the same pointer).
  •  

 

I wouldnt use type info for this, you can do this through the preprocessor with something like:

//create a hash based of a type name
inline uint64_t HashTypeName(const char* str)
{
  //djb2 algorithm from here:
  //http://www.cse.yorku.ca/~oz/hash.html
  uint64_t hash = 5381;
  int c;
  while((c = *str++) != 0)
  {
    hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
  }
  return hash;
}
 
#define HASHTYPE(type)\
    inline TypeHash HashType()\
    {\
      //a way of generating a hash that is cross platform\
      static TypeHash hash = HashTypeName(#type);\
      return hash;\
    }
 
Class MyType
{
public:
    HASHTYPE(MyType);
 
};

This needs less template magic than your solution and is generally easier to use because typeid names contain namespaces and such which can make it hard to figure out what a hash belongs too when you are debugging the code. In my case you can just run the HashTypeName("MyType") function in the watch window to find the hash of your type when on a breakpoint.

 

Alot of game engines have stuff like Runtime type info turned off in compile settings which will make typeid not work for dynamic types.

 

If you use a constexpr hash function all of the preprocessor stuff and dynamic lookup of typeid at runtime will be change to static compile time implementations.

Edited by NightCreature83

Share this post


Link to post
Share on other sites

You could use something like this is you dont need compile time ID

template<typename T>
struct quick_type_id{
    static void functionAsID() {}
}

and use

quick_type_id<int>::functionAsID
Edited by imoogiBG

Share this post


Link to post
Share on other sites

@samoth What you are describing is kind of what I am finding on the internet. I completely agree with your points.

@NightCreature83 I had to turn RTTI on yeah, and would prefer to have it off. I might actually go with a solution like that.

@imoogiBG I can't use that since it's not guaranteed that I get the same ID for a class on every run.

Share this post


Link to post
Share on other sites
  • From the most pedantic point of view, using the __func__ hack even makes your program a little more robust. The typeid operator does not guarantee that the same type_info object is returned for different invocations in the same program with the same type. This sounds like something you could take for granted, and this is probably what happens anyway, but in the strictest sense, that's not the case. The standard merely says that some type_info (or derived) object with static storage duration is returned (and leaving unspecified whether destructors are called), and that the objects from different typeid expressions with the same type compare equal. That doesn't mean that they are equal, or that that name() returns the same value (or even the same pointer).

 

Isn't this the entire point of type_index, so that type_index's made from different typeid's to the same type do compare equal?  From my understanding the real issue with type_info's being different for different types becomes relevant when were talking same types used from different dll's.

Share this post


Link to post
Share on other sites

For type checking in my Lua bindings, I use hashes generated by parsing __FUNCTION__

template<class T>
struct TypeName
{
	static void Get(const char*& begin, const char*& end)
	{
		begin = __FUNCTION__;
		for(++begin; *begin && *(begin-1) != '<'; ++ begin);
		for(end = begin; *end; ++ end);
		for(; end > begin && *end != '>'; -- end);
	}
	static const char* Get(Scope& a)
	{
		const char* begin=0, *end=0;
		Get(begin, end);
		uint length = end-begin;
		char* buf = (char*)a.Alloc(length+1);
		memcpy(buf, begin, length);
		buf[length] = 0;
		return buf;
	}
	static void Get(char* buf, uint bufLen)
	{
		const char* begin=0, *end=0;
		Get(begin, end);
		uint length = end-begin;
		eiASSERT( length+1 <= bufLen );
		memcpy(buf, begin, length);
		buf[length] = 0;
	}
	static const char* Get()
	{
		static const char* value = 0;
		if( !value )
		{
			static char buffer[256];
			Get(buffer, 256);
			//todo - memory fence
			value = buffer;
		}
		return value;
	}
};

template<class T>
struct TypeHash
{
	static u32 Get_()
	{
		const char* begin;
		const char* end;
		TypeName<T>::Get(begin, end);
		return Fnv32a(begin, end);
	}
	static u32 Get()
	{
		static const u32 value = Get_();
		return value;
	}
};

If you're talking about file I/O and format compatibility, you absolutely want manual IDs.

^^ This. Everything based on __func__, typeid, etc, is compiler dependent. You upgrade your compiler, or port to another platform, and all your type ID's will change.

Share this post


Link to post
Share on other sites

SeanMiddleditch is 100% correct. If you want to be able to load saved game states (eg: save games), you need to define the Id's in such a way that they can never change. The only way to do this is to assign them yourself.
 
Generating a hash from the name of the type seems like a good idea, but what if you ever decide to change the name of your class? For example if you decide that "LightComponent" should be "LightingComponent"?

 

Another thing you will need to consider is hash collisions. They are rare, but they can happen. Check out the hash collision tests done in the following stackexchange post: http://programmers.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
 
They found 7 collisions in the english dictionary using the DJB2 algorithm you're considering:
 

DJB2 collisions

  • hetairas collides with mentioner
  • heliotropes collides with neurospora
  • depravement collides with serafins
  • stylist collides with subgenera
  • joyful collides with synaphea
  • redescribed collides with urites
  • dram collides with vivency

Share this post


Link to post
Share on other sites

Isn't this the entire point of type_index, so that type_index's made from different typeid's to the same type do compare equal? From my understanding the real issue with type_info's being different for different types becomes relevant when were talking same types used from different dll's

That's right, the entire point of type_index is to make it work when it "shouldn't" work but that is what is intended. Means that two thing that are the same aren't the same, but the container still sees them as equal, so it somehow "works".

DLLs are not something C++ cares about (and many argue that they're "broken" because of mangling anyway). Though of course they might be the exact reason why typeid is deliberately specified in such a needlessly obnoxious way. I wouldn't know. But even so, that would be "mostly harmless" since nobody would expect types in any haphazard DLL possibly written by someone else to be identical with the ones in your program. How should that be possible.

Share this post


Link to post
Share on other sites

Be careful also if you use type_info::name() on a cross platform project, as the way the name is generated is platform dependent. You may end up with xml files that are not transferrable from one platform to another because the names are different. I haven't read the standard itself, but according to this it's not even guaranteed to generate different names for different types. That said I'm sure that in practise most compilers do.

 

I'd rather use manual IDs or macros. It's uglier but safer.

Share this post


Link to post
Share on other sites

 

Isn't this the entire point of type_index, so that type_index's made from different typeid's to the same type do compare equal? From my understanding the real issue with type_info's being different for different types becomes relevant when were talking same types used from different dll's

That's right, the entire point of type_index is to make it work when it "shouldn't" work but that is what is intended. Means that two thing that are the same aren't the same, but the container still sees them as equal, so it somehow "works".

DLLs are not something C++ cares about (and many argue that they're "broken" because of mangling anyway). Though of course they might be the exact reason why typeid is deliberately specified in such a needlessly obnoxious way. I wouldn't know. But even so, that would be "mostly harmless" since nobody would expect types in any haphazard DLL possibly written by someone else to be identical with the ones in your program. How should that be possible.

 

I think its more the situation where multiple DLL's are generated from the same header files.  You'd have identical types but the type_info's could be different objects (in fact would probably have to be).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Vu Chi Thien
      Hi fellow game devs,
      First, I would like to apologize for the wall of text.
      As you may notice I have been digging in vehicle simulation for some times now through my clutch question posts. And thanks to the generous help of you guys, especially @CombatWombat I have finished my clutch model (Really CombatWombat you deserve much more than a post upvote, I would buy you a drink if I could ha ha). 
      Now the final piece in my vehicle physic model is the differential. For now I have an open-differential model working quite well by just outputting torque 50-50 to left and right wheel. Now I would like to implement a Limited Slip Differential. I have very limited knowledge about LSD, and what I know about LSD is through readings on racer.nl documentation, watching Youtube videos, and playing around with games like Assetto Corsa and Project Cars. So this is what I understand so far:
      - The LSD acts like an open-diff when there is no torque from engine applied to the input shaft of the diff. However, in clutch-type LSD there is still an amount of binding between the left and right wheel due to preload spring.
      - When there is torque to the input shaft (on power and off power in 2 ways LSD), in ramp LSD, the ramp will push the clutch patch together, creating binding force. The amount of binding force depends on the amount of clutch patch and ramp angle, so the diff will not completely locked up and there is still difference in wheel speed between left and right wheel, but when the locking force is enough the diff will lock.
      - There also something I'm not sure is the amount of torque ratio based on road resistance torque (rolling resistance I guess)., but since I cannot extract rolling resistance from the tire model I'm using (Unity wheelCollider), I think I would not use this approach. Instead I'm going to use the speed difference in left and right wheel, similar to torsen diff. Below is my rough model with the clutch type LSD:
      speedDiff = leftWheelSpeed - rightWheelSpeed; //torque to differential input shaft. //first treat the diff as an open diff with equal torque to both wheels inputTorque = gearBoxTorque * 0.5f; //then modify torque to each wheel based on wheel speed difference //the difference in torque depends on speed difference, throttleInput (on/off power) //amount of locking force wanted at different amount of speed difference, //and preload force //torque to left wheel leftWheelTorque = inputTorque - (speedDiff * preLoadForce + lockingForce * throttleInput); //torque to right wheel rightWheelTorque = inputTorque + (speedDiff * preLoadForce + lockingForce * throttleInput); I'm putting throttle input in because from what I've read the amount of locking also depends on the amount of throttle input (harder throttle -> higher  torque input -> stronger locking). The model is nowhere near good, so please jump in and correct me.
      Also I have a few questions:
      - In torsen/geared LSD, is it correct that the diff actually never lock but only split torque based on bias ratio, which also based on speed difference between wheels? And does the bias only happen when the speed difference reaches the ratio (say 2:1 or 3:1) and below that it will act like an open diff, which basically like an open diff with an if statement to switch state?
      - Is it correct that the amount of locking force in clutch LSD depends on amount of input torque? If so, what is the threshold of the input torque to "activate" the diff (start splitting torque)? How can I get the amount of torque bias ratio (in wheelTorque = inputTorque * biasRatio) based on the speed difference or rolling resistance at wheel?
      - Is the speed at the input shaft of the diff always equals to the average speed of 2 wheels ie (left + right) / 2?
      Please help me out with this. I haven't found any topic about this yet on gamedev, and this is my final piece of the puzzle. Thank you guys very very much.
    • By Estra
      Memory Trees is a PC game and Life+Farming simulation game. Harvest Moon and Rune Factory , the game will be quite big. I believe that this will take a long time to finish
      Looking for
      Programmer
      1 experience using Unity/C++
      2 have a portfolio of Programmer
      3 like RPG game ( Rune rune factory / zelda series / FF series )
      4 Have responsibility + Time Management
      and friendly easy working with others Programmer willing to use Skype for communication with team please E-mail me if you're interested
      Split %: Revenue share. We can discuss. Fully Funded servers and contents
      and friendly easy working with others willing to use Skype for communication with team please E-mail me if you're interested
      we can talk more detail in Estherfanworld@gmail.com Don't comment here
      Thank you so much for reading
      More about our game
      Memory Trees : forget me not

      Thank you so much for reading
      Ps.Please make sure that you have unity skill and Have responsibility + Time Management,
      because If not it will waste time not one but both of us
       

    • By RoKabium Games
      We've now started desinging the 3rd level of "Something Ate My Alien".
      This world is a gas planet, and all sorts of mayhem will be getting in our aliens way!
      #screenshotsaturday
    • By Pacoquinha Studios
      Kepuh's Island is Multiplayer 3D Survival Game where you survive on the Kepuh's Islands, confronting challenges that are not only other players but also bosses, and even the environment itself.
      We have a lowpoly faster battle-royale idea, where about 12 players on the map fighting for survival! Also adding some more things into that style such as bosses around the map giving you abilities and much more such as vehicles, weapons, skins, etc...
      Now we are on cartase which is a crowdfunding online which purpose is to raise funds for the development of the game. Come and be part of this development.
      Link for Cartase: https://www.catarse.me/kepuhsisland?ref=project_link
      We post updates and trailers on
      Twitter: https://twitter.com/pcqnhastudios
      Facebook: https://www.facebook.com/pacoquinhastudios/
      Site: http://pacoquinhastudios.com.br
      If you could check out it would be great
      Thnks
      Some images:





  • Advertisement