- Home /
Accessing instances of a component efficiently
Issue:
I'd like to have a static field inside a component. I'm trying to figure out if this is going to come back to bite me.
The use case is that I have a component, called Graspable, and a related component called Grasper. Each Graspable instance has a field for a Collider, which I assign in the editor.
Instances of Grasper monitor the collision system by calling Physics.OverlapSphere. When a Grasper detects a Collider nearby, I want it to find the Graspable instance (if any) which is associated with that Collider.
It occurred to me I could maintain a static associative table in Graspable, with Colliders as keys and Graspables as values. Graspable objects can add themselves to the table, and Graspers can use the table to find the Graspable associated with any given Collider. This method would allow me to relate any Graspable to any Collider. I could potentially use a data structure like a hash table to make these lookups efficient.
I am clueless about what problems this is going to create. I just know that using static fields is tricky. Can anybody help me understand whether this is a good idea, and whether I have any other options? I haven't found another solution that doesn't involve some sort of static or singleton entity, and this seems to be the most elegant solution I've found.
Why I don't want to use GetComponent:
Here is an example of a reason I don't want to use GetComponent. The GetComponent family of functions can only obtain components on the same GameObject, or on children or parents of that GameObject. I want to be able to make objects with this structure in the transform hierarchy:
GameObject parent(Graspable, Transform, etc.)
GameObject child1(Collider, Transform)
GameObject child2(Collider, Transform)
GameObject child3(Collider, Transform)
I only want one of the Colliders on the children to be associated with the Graspable component on the parent. To achieve this with GetComponent, I will need to search up the tree with GetComponentInParent, then check the collider field in the Graspable component to see if it matches the collider I'm checking. This means that if there are lots of colliders in close proximity I will be calling GetComponentInParent several times. I don't want to waste time searching through trees that don't contain Graspables, which is why I want the relation between a Collider and a Graspable to be efficiently queriable. While I might be able to solve this problem using layer masks, there are a limited number of layers available and I could imagine needing many different systems using this type of functionality.
Answer by JVene · Sep 06, 2018 at 07:06 AM
While I'm unclear on a few points, I'm certain there are several options to consider, and using a Dictionary isn't a bad one. I sense another is slightly better, but that requires a few assumptions about those points on which I'm not entirely clear.
One point I'm not certain about is why only one child collider is to be associated with the parent Graspable, when the hierarchy you list shows a Graspable with three child GameObjects each with a collider component. OverlapSphere will return possibly all 3 of these children, or whatever combination fits in or on the sphere, and perhaps what you require is that only 1 notification is processed on the parent Graspable. Still, for a Dictionary, it would seem to me that all 3 must be keyed (lest two colliders would never be found on lookup). As I said, this is a point upon which I'm unclear, but that may not matter if the following is applicable to you.
The collider(s) given from a call to OverlapSphere each have a gameObject property, from which the transform provides the parent property, which should provide the GameObect instance owning the Graspable component (given the hierarchy you've illustrated), from which you could call GetComponent for the Graspable Monobehaviour derivative that parent owns. While that plan does utilize GetComponent, it is directly on the owning GameObject and therefore doesn't search far and wide through a hierarchy. For example (exaggerated pseudo code):
foreach( Collider c in colliders_from_overlap_sphere )
{
GameObject obj_child = c.gameObject;
Transform obj_child_transform = obj_child.transform;
GameObject parent_owning_graspable = obj_child_transform.parent;
Graspable g = parent_owning_graspable.GetComponent< Graspable >() as Graspable;
/* use g */
}
The point here is to illustrate that from c, the Collider, to parent_owning_graspable are fairly quick reference 'jumps' (a pointer to a pointer to a pointer). These are fast, given what they are, relative to something like a find or a GetComponentInParent. One might say they are 'direct' accesses.
The parent_owning_graspable GameObject, therefore, may use a call to GetComponent as a rather 'direct' means of finding the component, in that it is the parent that owns the component (unless I misunderstand the implications of your hierarchy). There is a search performed by GetComponent, but that is relative to the volume of components on the object.
Now, compare the alternative. If one forms a Dictionary (implemented as a hash table under the hood) to store keyed data, I assume the purpose is to look up a collider in the list from OverlapSphere, and find from that a reference to the Graspable component. The question (which you may have in mind as a theory, hinted from your post) is will this be faster or slower than the approach above. Since the only search performed in that plan is within GetComponent, this becomes a comparison of the performance between that search and the search performed within Dictionary to find the match. Certainly it is logically direct, a collider returns a Graspable by key. Searching for a hash, however, is not exactly low cost or cost free (and it can depend on the cost of the hash function).
One key to prediction is to know the likely number of components that might be attached to the parent GameObject owning a Graspable, and the number of colliders in the Dictionary. Searches are quite fast, so volumes under 10 are likely comparable in both case, as much about overhead to perform the search as the search itself. However, it is not likely the parent owning the Graspable would have more than 10 attached components to search through, and perhaps you could contrive a means of making sure the volume is small, but only you know how many entries may ultimately be in the Dictionary. As that volume grows, the lookup will take a bit longer, to an extent that it may well take longer than the approach I've outlined above (using collider's gameObject.transform.parent.GetComponent() approach ). That, of course, assumes I correctly understand the implications of your hierarchy.
Now, if you do choose a Dictionary, implemented as a static member, there are no serious problems beyond the standard caveats. Those start with the general warning about avoiding global data unless it is genuinely global, and initialization must be controlled well (which is to say, only once). Beyond that, if there are threads which can conflict, they must be coordinated (often with locks, hopefully with lock free methods where applicable). From what I understand of your stated intent, a static Dictionary is a good fit. The Start or Awake in Graspable would call a static method which conditionally initializes a new Dictionary if required (lazy initialization avoids order of execution issues), but otherwise 'registers' the collider/Graspable keys as part of that initialization (which can involve the costly searching and getting required, but then only once at initialization). That can be on the occasion that new objects are instantiated during the game.
@JVene, thank you for your thoughtful reasponse. I am going to Accept your answer but I want to try to clarify both the unclear points from above and the changes in my thinking about the problem since I posted the question last night.
First, topology. The hierarchy I showed was indeed the simplest case. In reality, I was picturing having lots of children, potentially children-of-children (and so on). This is what necessitated the search. Furthermore, the idea is that I might have several different components up on the parent GameObject, each using this type of functionality. I wouldn't want the collider I am using for grasping, say, a control lever to also trigger the $$anonymous$$onoBehavior for picking up the whole object by a handle.
This is where your answer really helped me. I realized that one major use case I was considering was having multiple Graspables on the same GameObject. An example of this would be a control panel with lots of levers. Somehow I was in the $$anonymous$$dset that all my functionality should be in $$anonymous$$onoBehaviors on the "top level" GameObject of that control panel prefab. I am now realizing that this is not how Unity is designed; indeed, I am actually prevented from having multiple "Graspable" components on the same GameObject. Ins$$anonymous$$d, I should have GameObjects for each lever, each comprising the colliders and the Graspable components, as children of the higher level object. In that case I can use the method you described. This would be akin to having different entities for each lever in an ECS framework.
In short, I think that this boiled down to my misunderstanding the intended usage of Unity. I would be interested to know whether you agree with my understanding here. Regardless, I'll still mark your response as the accepted answer since it helped me so much already.
It sounds to me that you've extrapolated from the simple case hierarchy relative to my suggestions and applied it to the more complex general cases, with a realization that the relationships can be arranged to facilitate better access (a better 'geometry' to the hierarchy). I assume you mentally reversed the gameObject.transform.parent.GetComponent 'phrase' into a refactoring of structure which becomes functionality.
I sensed but couldn't be sure that the hierarchy was a simple case illustration, but I'm glad all worked toward advancement of your project.
This is also an example that proves my own pet theory about exchanges on forums like this. Some prefer short, terse answers, and that's understandable, but more thorough 'thought walk thru' posts tend to prompt more productive re-thinking.
Your conclusion about "walk-thru" vs. atomic answers is, I think, correct. I wrote my question several times, trying to reduce it to a very simple question. I suppose if I wanted to, I could have reduced my question to "how can I create arbitrary relationships between components?"
This all started for me because I was trying to avoid calling GetComponent, ever. It seemed wrong to build a game in a way that requires frequent introspection. As I read more Unity examples, I am realizing that GetComponent is a key feature of the compositional architecture of the engine. Without GetComponent, I would need to explicitly define relationships between all the components of an entity, essentially implementing my own entity-component table.
I do wonder if Unity includes optimizations for this. People talk about GetComponent as being an introspective call, but does Unity optimize that introspection in an intelligent way? It seems like it would be worthwhile for the engine to make queries of components within the same GameObject extremely efficient.
Answer by misher · Sep 06, 2018 at 07:25 AM
Why are you using Physics.SphereOverlap if you can use Triggers. Attach sphere collider of desired radius on your Grasper objects and make it a trigger. Then you can use simple unity callback OnTriggerEnter
Your answer
Follow this Question
Related Questions
parenting and static colliders 0 Answers
Multiple colliders on one object? Or changing variables on a specific clone. 1 Answer
Unity 5 Static Collider performance 1 Answer
Can static objects be enabled/disabled? 1 Answer
Controll squeeze of rigidbody between static collider and kinematic rigidbody. 1 Answer