Baker.GetComponent<T> should not restrict T : Component

In the Baker API, the methods GetComponent<T>(..) and GetComponents<T>(..) should not restrict where T : Component, just like GameObject.GetComponent does not. It prevents us from using interfaces to fetch components. Example:

[DisallowMultipleComponent]
public class StatsAuth : MonoBehaviour
{
    public interface ICollect
    {
        void CollectStats(List<(StatType, float)> stats, IBaker baker);
    }

    class Baker : Baker<StatsAuth>
    {
        public override void Bake(StatsAuth auth)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            var stats = AddBuffer<StatElement>(entity);
            List<(StatType, float)> elements = new();

            var components = GetComponents<MonoBehaviour>(); // USED HERE
            foreach (var component in components)
            {
                if (component is ICollect statAuth)
                {
                    statAuth.CollectStats(elements, this);
                }
            }

            foreach (var item in elements)
            {
                stats.Add(new()
                {
                    ...
                });
            }
        }
    }
}


[DisallowMultipleComponent]
[RequireComponent(typeof(StatsAuth))]
public class HealthAuth : MonoBehaviour, StatsAuth.ICollect
{
    public int Health;
    
    void StatsAuth.ICollect.CollectStats(List<StatsAuth.Element> stats, IBaker baker)
    {
        stats.Add((StatType.HealthMax, HP));
    }
}

The GetComponents<MonoBehaviour>() ends up adding unneeded dependencies.

NB: We mostly use this pattern when having to fill up a DynamicBuffer from multiple authoring component. If, for some reason, there is a technical limitation that makes you not want to support this use case, it’d be great if there was an alternative or more elegant way to do it.

Entities version used: 1.1

You can turn this around

public interface ISmartObjectAffordanceAuthoring
{
    void AddDefaultAffordances(DynamicBuffer<SmartObjectAffordance> buff);
}

public class SmartObjectAuthoring : MonoBehaviour
{
    class Baker : Baker<SmartObjectAuthoring>
    {
        public override void Bake(SmartObjectAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.None);

            var buff = AddBuffer<SmartObjectAffordance>(entity);
            var bakers = authoring.GetComponents<ISmartObjectAffordanceAuthoring>();
            foreach (var baker in bakers)
            {
                baker.AddDefaultAffordances(buff);
            }
        }
    }
}

I think you are mistaking GetComponents with authoring.GetComponents

edit:
Ah, you want to gather DOTS components in a dynamic buffer? Or iterate them?

This is not correct, as it breaks incremental baking tracking. That’s likely also the reason why interfaces aren’t supported.

If you truly wanted to add this, the best way I can think of would be to write an extension method that looks up all MonoBehaviours implementing the particular interface from a static cache. And if that cache doesn’t exist (which always happens the first time), then it uses UnityEditor.TypeCache to populate it.

This is something I’ve considered adding to my framework for a while now, but have kept procrastinating.

DependsOn should fix this? At least the direct reference and then DependOn on other managed objects (if any) in .AddDefaultAffordances again. I thought this way quite a convenient way to do it, I’d be quite sad if this wasnt properly supported.

DependsOn won’t detect the addition of a new component.

I took a different approach for the same issue, filling up a DynamicBuffer from multiple authoring components. I create a buffer authoring component, then assign a reference to it in other authoring components that need to add themselves to that buffer. It seems to work flawlessly, but maybe not the cleanest since you have to assign the buffer authoring to all components that need to add themselves. I think you also have to make sure the buffer authoring is before components that need to access the dynamic buffer.

public class AbilityAuthoring : MonoBehaviour
{
    //Other authoring components add themselves to this buffer
    public DynamicBuffer<Ability> abilityBuffer;

    public class Baker : Baker<AbilityAuthoring>
    {
        public override void Bake(AbilityAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            authoring.abilityBuffer = AddBuffer<Ability>(entity);
        }
    }
}

I like the idea, but isn’t problematic for dependency tracking? How do other components (e.g. JumpAbilityAuthoring) tell the AbilityAuthoring’s baker that there is a dependency?

If no dependency is declared, then modifying a value in JumpAbilityAuthoring might not trigger a correct incremental re-bake.

I think you might be right about this, I just tried changing a value on an instance in a subscene and it throws error and doesn’t update the buffer value.

It hasn’t been a problem for me since I do everything in prefabs. I change a value in a prefab and it rebakes with the correct value.

After trying out your pattern, I see the benefits and better understand how authoring dependencies work. It is a pain that you can’t use the interface in GetComponents. I assume that is the function that is doing the dependency part.

I think I found a workaround that still keeps dependencies. Just make an abstract Monobehaviour that implements the interface and use it inside GetComponents

// Abstract base class that other Stat components will derive from
public abstract class StatAuthoring : MonoBehaviour, StatAuthoring.ICollect
{
    public interface ICollect
    {
        StatData GetStat(IBaker baker);
    }

    public abstract StatData GetStat(IBaker baker);
}
public class StatsAuthoring : MonoBehaviour
{
    class Baker : Baker<StatsAuthoring>
    {
        public override void Bake(StatsAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            var stats = AddBuffer<StatData>(entity);

            var components = GetComponents<StatAuthoring>();

            foreach (var component in components)
            {
                stats.Add(component.GetStat(this));   
            }
        }
    }
}