Disabling built-in package doesn't remove function calls in profiler

Hi all,

When profiling my project, I noticed calls to:

  • EarlyUpdate.Physics2DEarlyUpdate
  • FixedUpdate.Physics2DFixedUpdate (0.003ms)
  • FixedUpdate.PhysicsClothFixedUpdate
  • FixedUpdate.DirectorFixedUpdatePostPhysics
  • PreUpdate.Physics2DUpdate
  • PreUpdate.AIUpdate (0.003ms)
  • PreUpdate.WindUpdate
  • PreUpdate.UpdateVideo
  • Update.DirectorUpdate
  • PreLateUpdate.AIUpdatePostScript
  • PreLateUpdate.DirectorUpdateAnimationBegin
  • PreLateUpdate.DirectorUpdateAnimationEnd
  • PreLateUpdate.Physics2DLateUpdate (0.002ms)
  • PostLateUpdate.AIUpdatePostScript
  • PostLateUpdate.DirectorLateUpdate
  • PostLateUpdate.PhysicsSkinnedClothBeginUpdate
  • PostLateUpate.UpdateVideoTextures
  • PostLateUpate.UpdateVideo
  • PostLateUpate.DirectorRenderImage
  • PostLateUpate.PhysicsSkinnedClothFinshUpdate
    (and maybe more)

I know it’s only 0.001ms to 0.003ms each (this is negligable) but there is 15+ useless calls and I don’t use Cloth, 2D Physics, AI navigation, Director nor Wind built-in packages. I went to the built-in package list and disabled the packages I don’t use (Unity - Manual: Disable a built-in package).

After disabling them, the calls are still present in the editor and in builds. It does the same with a fresh new empty project and an empty scene. Tested with 2022.3.1f1 on Windows 10, Mono and IL2CPP with max stripping.

What’s the point of disabling built-in package if it doesn’t remove this useless overhead?

Related post (more focused on build size than runtime performance): Exclude Packages from Build
Unity Issue Tracker - Build-in Packages modules are being included in the project folder when they are disabled in the Package manager (the fact it’s closed “by-design” irritates me a bit…)

Because the various stages of the player loop are defined elsewhere, not related to these packages: UnityCsReference/Runtime/Export/PlayerLoop/PlayerLoop.bindings.cs at 18e481fd5dbda630b82f4f4c59a2a0164ed75ebd · Unity-Technologies/UnityCsReference · GitHub

If you really want to remove them you can use the PlayerLoop API and pull out as much as you like from the various player loop stages as you want.

Thank you!

I didn’t know much about this API.

After your message, I found your thread about the PlayerLoop API ( Prevent Custom Update Loop running post playmode ) with some usefull informations and links.

I managed to remove these useless calls using this script:

using System;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;

public static class PlayerLoopCleaner
{
    private static readonly Type[] typesToRemove = new Type[] {
        // Physics 2D
#if UNITY_2022_2_OR_NEWER
        typeof(EarlyUpdate.Physics2DEarlyUpdate),
#endif
        typeof(FixedUpdate.Physics2DFixedUpdate),
        typeof(PreUpdate.Physics2DUpdate),
        typeof(PreLateUpdate.Physics2DLateUpdate),

        // Director
        typeof(Initialization.DirectorSampleTime),
        typeof(FixedUpdate.DirectorFixedSampleTime),
        typeof(FixedUpdate.DirectorFixedUpdate),
        typeof(FixedUpdate.DirectorFixedUpdatePostPhysics),
        typeof(Update.DirectorUpdate),
        typeof(PreLateUpdate.DirectorUpdateAnimationBegin),
        typeof(PreLateUpdate.DirectorUpdateAnimationEnd),
        typeof(PreLateUpdate.DirectorDeferredEvaluate),
        typeof(PostLateUpdate.DirectorLateUpdate),
        typeof(PostLateUpdate.DirectorRenderImage),

        // AI
        typeof(PreUpdate.AIUpdate),
        typeof(PreLateUpdate.AIUpdatePostScript),

        // Wind
        typeof(PreUpdate.WindUpdate),

        // Cloth
        typeof(PostLateUpdate.PhysicsSkinnedClothBeginUpdate),
        typeof(PostLateUpdate.PhysicsSkinnedClothFinishUpdate),
   
        // Old network system
        typeof(PreLateUpdate.UpdateNetworkManager)
    };

    private static readonly string[] typesToRemoveAsString = new string[] {
        // Cloth internal types
        "UnityEngine.PlayerLoop.FixedUpdate+PhysicsClothFixedUpdate",
        "UnityEngine.PlayerLoop.PreUpdate+PhysicsClothUpdate"
    };


    [RuntimeInitializeOnLoadMethod]
    private static void RemoveUnusedPackageFromPlayerLoop()
    {
        PlayerLoopSystem playerLoop = PlayerLoop.GetCurrentPlayerLoop();
        //DisplayRecursivly(0, playerLoop);
        foreach(Type type in typesToRemove)
        {
            TryRemoveTypeFrom(ref playerLoop, type);
        }
        foreach (string type in typesToRemoveAsString) {
            TryRemoveTypeFrom(ref playerLoop, type);
        }
        //DisplayRecursivly(10, playerLoop);
        PlayerLoop.SetPlayerLoop(playerLoop);
    }

    private static void DisplayRecursivly(int level, PlayerLoopSystem system)
    {
        Debug.Log(level + " " + system.type);
        if (system.subSystemList != null)
        {
            Debug.Log(level + " subSystemList.Length: " + system.subSystemList.Length);
            foreach (PlayerLoopSystem sys in system.subSystemList)
            {
                DisplayRecursivly(level + 1, sys);
            }
        }
    }

    /// <summary>
    /// From https://github.com/Baste-RainGames/PlayerLoopInterface/blob/a4c15199ecb5f88b8a5009d0c391b967d768068c/Runtime/PlayerLoopInterface.cs#L136
    /// </summary>
    /// <param name="currentSystem"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    private static bool TryRemoveTypeFrom(ref PlayerLoopSystem currentSystem, Type type)
    {
        PlayerLoopSystem[] subSystems = currentSystem.subSystemList;
        if (subSystems == null)
        {
            return false;
        }

        for (int i = 0; i < subSystems.Length; i++)
        {
            if (subSystems[i].type == type)
            {
                PlayerLoopSystem[] newSubSystems = new PlayerLoopSystem[subSystems.Length - 1];

                Array.Copy(subSystems, newSubSystems, i);
                Array.Copy(subSystems, i + 1, newSubSystems, i, subSystems.Length - i - 1);

                currentSystem.subSystemList = newSubSystems;

                return true;
            }

            if (TryRemoveTypeFrom(ref subSystems[i], type))
            {
                return true;
            }
        }

        return false;
    }

    private static bool TryRemoveTypeFrom(ref PlayerLoopSystem currentSystem, string type)
    {
        PlayerLoopSystem[] subSystems = currentSystem.subSystemList;
        if (subSystems == null) {
            return false;
        }

        for (int i = 0; i < subSystems.Length; i++) {
            if (subSystems[i].type.ToString() == type) {
                PlayerLoopSystem[] newSubSystems = new PlayerLoopSystem[subSystems.Length - 1];

                Array.Copy(subSystems, newSubSystems, i);
                Array.Copy(subSystems, i + 1, newSubSystems, i, subSystems.Length - i - 1);

                currentSystem.subSystemList = newSubSystems;

                return true;
            }

            if (TryRemoveTypeFrom(ref subSystems[i], type)) {
                return true;
            }
        }

        return false;
    }
}

I benchmarked on an empty project with empty scene and just the script above. It sems to gain 0.050ms (on an old i5 3rd gen 4 cores 4 threads @3.10GHz). I’m not sure it’s worth the time to develop this, but I least I have learned something :slight_smile:

Now I have two more questions:

3 Likes

Because there’s a lot more that can go wrong here than just having all the player loop systems present by default. The overhead of having them is completely negligible in 99% of cases.