How big is the impact of not caching type handles?

I didn’t know that you can cache type handles in OnCreate() then just call Update() on them prior to usage in OnUpdate() until it was mentioned in the latest change log. We are very used to just calling the Get*TypeHandle() when scheduling jobs in OnUpdate(). This is going to be a huge refactor but maybe we don’t have to this? Our game is doing fine without the caching but since the change log says that it’s more efficient to do so, I’m having doubts.

If it ain’t broken don’t fix it.
If you have no performance issue don’t worry about it IMO.
Just add it to your best practices for your next projects/systems.

1 Like

This mostly helps in situations where you have lots of overhead from lots of systems but need them to be managed SystemBase systems. If it is not a problem, don’t worry about it.

5 Likes

I can give you some numbers, on a slower platform (xbox one) in a system with a lot of dependencies (25+) main thread went from 1.35 to 0.67ms caching them.

The real cost is every call iterates an unsafe list of all dependencies to check to see if it needs to be added. This is slow once your dependency count gets high.

On a system with only a couple of dependencies benefit is small, but under the right circumstances it’s pretty huge.

6 Likes

Thanks. That’s good information.

What do you mean by “dependency” here? Is it the chaining of this.Dependency = job.Schedule(…, this.Dependency)? Or is it something else?

Dependencies that have been added to the system.

All dependencies in a query but also all calls to GetComponentTypeHandle, GetComponentFromEntity, (and buffer equiv etc) add a dependency to the system.

2 Likes
        public ComponentTypeHandle<T> GetComponentTypeHandle<T>(bool isReadOnly = false) where T : struct, IComponentData
        {
            AddReaderWriter(isReadOnly ? ComponentType.ReadOnly<T>() : ComponentType.ReadWrite<T>());
            return EntityManager.GetComponentTypeHandle<T>(isReadOnly);
        }

AddReaderWriter

public static bool Add(ComponentType type, ref UnsafeList<int> reading, ref UnsafeList<int> writing)
{
    Assert.IsFalse(type == ComponentType.ReadWrite<Entity>());

    if (type.IsZeroSized)
        return false;

    if (type.AccessModeType == ComponentType.AccessMode.ReadOnly)
        return AddReaderTypeIndex(type.TypeIndex, ref reading, ref writing);
    else
        return AddWriterTypeIndex(type.TypeIndex, ref reading, ref writing);
}

public static bool AddReaderTypeIndex(int typeIndex, ref UnsafeList<int> reading, ref UnsafeList<int> writing)
{
    if (reading.Contains(typeIndex))
        return false;
    if (writing.Contains(typeIndex))
        return false;

    reading.Add(typeIndex);
    return true;
}

public static bool AddWriterTypeIndex(int typeIndex, ref UnsafeList<int> reading, ref UnsafeList<int> writing)
{
    if (writing.Contains(typeIndex))
        return false;

    var readingIndex = reading.IndexOf(typeIndex);
    if (readingIndex != -1)
        reading.RemoveAtSwapBack(readingIndex);

    writing.Add(typeIndex);
    return true;
}

Basically calling this every frame for every Xhandletype/getXdata you do in your update (this runs must faster in burst though btw)

3 Likes

So for lambda based jobs I assume this is not automatic?