#Adding a Component Object to an entity using its parent type

1 messages · Page 1 of 1 (latest)

molten elbow
#

I have a MonoBehaviour of type B, which inherits from type A. I want to add an instance of B to an entity but I need a System to retrieve those entities also by the type A.

So I tried this:

commandBuffer.AddComponent(newEntity, myInstance);
commandBuffer.AddComponent<A>(newEntity, myInstance);```
But this generates an error on play:
```SetComponentObject doesn't match the specified component type```
This sound stupid to me. Why DOTS doesn't let me add a pointer to my instance with the type I want?
#

This is the internal check that DOTS performs:

                throw new System.ArgumentException($"SetComponentObject {componentObject.GetType()} doesn't match the specified component type: {TypeManager.GetType(componentType.TypeIndex)}");```
versed stag
#

but one easy alternative - you can just have a wrapper object

#

of specific type

#

which holds reference to whatever abstract you want

molten elbow
#

Thank you @versed stag , that is my current solution

sick iron
#

If I understand correctly, the proposed fix would be to use something like Type.IsAssignableFrom() instead of an exact type match, when the component type is a managed object?

versed stag
#

generic version would just use whatever object is casted to

#

but I'd say the latter would be better

sick iron
#

Does using IsAssignableFrom() (or some appropriate "is A either a B or a subclass of B?" check) not solve the problem? We're trying to keep API changes to a minimum for 1.x releases, so a one-line internal change to relax an existing check has some appeal

versed stag
rotund comet
# sick iron If I understand correctly, the proposed fix would be to use something like `Type...

Looks like a good solution to me, and I can't imagine that would break anything. It seems to me that at worst it won't give someone a compiler error where it would before and he/she is unintentionally casting the object. 🤷

I suppose it could get a little odd if people expect more polymorphism interop, e.g. to be able to do Get<A> with something that was Set<B> or vice-versa, which is obviously not going to work.

sick iron
#

I've filed this request internally; we'll figure out which fix makes the most sense. In the meantime, thank you for describing the workaround!

versed stag
sick iron
#

As in, make all GetComponent() calls use the managed-component path? No, absolutely not. Perish the thought.

versed stag
sick iron
#

Ah, I missed the GameObject prefix in your question

versed stag
#

My only wish for this whole thread - expose internal method that sets object with specified type you provide, instead of always calling GetType() on it, as AddComponentObject does

#

so this way I can add class Foo : Bar, as Bar to entity, instead of relying on exposing internals/using class wrapper with Bar field.

sick iron
#

I've reviewed the available managed-component APIs right now, for completeness. Assuming you have class Base : IComponentData and class Derived : Base

  1. EntityManager.AddComponentObject(Entity,object) works with arbitrary managed objects (whether or not they implement IComponentData) and doesn't take a target type, so it calls GetType() on the provided object and adds the component with that type. So, passing a Derived object here adds the component of type Derived.
  2. EntityManagerManagedComponentExtensions.AddComponentData<T>(Entity,T) works with managed IComponentData only. So, I would expect AddComponentData<Foo>(Entity, Bar value) to successfully add a component of type Foo, but instead you get a runtime error that the provided value isn't of type Foo, as reported in the original post of this thread.
  3. EntityManager.SetComponentObject(Entity, ComponentType, object) is internal, as you've noted. It internally checks that object.GetType() matches the provided ComponentType, so even if we made it public, it wouldn't handle the original base/derived issue. This function is used internally by #2 and #4, and this is the check that fails in the OP's case.
  4. EntityManagerManagedComponentExtensions.SetComponentData<T>(Entity,T) has the same problem as the managed AddComponentData<T>().
#
  1. EntityManager.GetComponentObject<T>(Entity) looks for a component of the provided type. It must match exactly. If an entity has a component of type Base, then GetComponentObject<Derived>(e) would throw. If the entity has a component of type Derived, then GetComponentObject<Base>(e) would throw. You can, of course, cast the return value to whatever object type you want, assuming it's a valid C# cast.
  2. EntityManager.GetComponentObject<T>(Entity, ComponentType) looks for a component of the provided ComponentType, but automatically casts it to type T. So if the entity has Derived, GetComponentObject<Base>(e, ComponentType.ReadWrite<Derived>()) will work correctly, giving you the Derived component value type-cast to a Base object.
  3. There's also EntityManagerManagedComponentExtensions.GetComponentData<T>(Entity), but it looks identical to EntityManager.GetComponentObject<T>(Entity) aside from some extra constraints on T, so maybe it's redundant?
#

My proposal was to change SetComponentObject to use "is object either componentType, or derived from componentType" instead of "is object literally a componentType", so that AddComponentData<Base>(Entity, Derived) and SetComponentData<Base>(Entity, Derived) (#2 and #4) work. This would allow a managed component of type Base to also store values derived from Base.

#

Your proposal, if I understand correctly, is to expose SetComponentObject(Entity, ComponentType, object) and remove its type-check entirely so that it always sets the provided component to the provided object, regardless of whether the object's type is at all related to the component's type -- is that correct?

#

GetComponent/GetComponentObject wouldn't change either way, beyond the side effect that if a managed component of type Base could store values of type Derived, then GetComponentObject<Base>(entity) could return an object (of type Base) that happens to be castable to Derived. It sounds like you don't want that, though -- why is that?

#

If I have some fundamental misunderstanding about how derived classes work in C#/GameObjects, please let me know. It wasn't exactly my deep passion for traditional OOP C# that drew me to the DOTS team, so there may very well be gaps in my knowledge here 🙂

sick iron
#

@issue when you have a moment, can I get your feedback on my wall of text above so that I can file an accurate ticket for this request?

versed stag
versed stag
#

So far all you explained is correct and your proposal is good to me as well

sick iron
#

Alignment! Thank you, onto the queue it goes

sick iron
#

I dug a bit further, and I'm afraid this isn't as simple as it seemed at first. The ManagedComponentStore expects to be able to ask one of its stored component values "hey, what component type are you?" and get a single, unambiguously correct answer. This wouldn't work if, for example, you registered both MyBaseClass and MyDerivedClass as component types, and assigned a value of type MyDerivedFromDerivedClass.
Making the necessary changes to the MCS to allow us to safely violate this assumption is probably more risk than we're ready to accept for the Entities 1.x timeframe, so I suspect this will end up in a longer-term pile of future feature requests with no clear ETA. Sorry to be the bearer of bad news.