Search Unity

Animation.RebuildInternalState ms spikes.

Discussion in 'iOS and tvOS' started by Futurerobot, Feb 15, 2012.

  1. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    I'm using a spawnpool for enemies in my project which instantiates objects on start and then activates deactivates them as they are killed and reused. I see that whenever I enable the animation component I get spikes of 75ms in the profiler on an iphone4G, causing noticable stutters while fighting enemies.

    I've seen this is a general issue, but didn't find much about it except from sandworm at unity answers saying:

    "I was just at Unite 11, and I asked the Unity developers there. They said that it's a known issue, and it gets called on enable or disable of the animation component (or the object). They recommended pooling your objects, rather than instantiating them in-game, and leaving them in an active, offscreen state."

    I tested this by having active animations offscreen with enemies in a "do nothing" state, and it got rid of the spikes, but the cost of running multiple animation updates reduced the overall fps too much.

    I'm wondering about a couple of things.
    Does 75ms seem like a "normal" spike for this?
    Does it depend on amount of bones, amount of animations in the component, both?
    Since it was mentioned as a "known issue" , is this considered to be a bug up for fixing? Or is it simply a fair cost for enabling an animation component.
    Has anyone worked around this in a nice way? Some tricks on how to go about a spawning pool with active, offscreen animation components in the cheapest way possible?

    Any help appreciated! :)
     
  2. MikaMobile

    MikaMobile

    Joined:
    Jan 29, 2009
    Posts:
    845
    There shouldn't be any overhead for having an active animation component that is simply off screen, as long as you don't have Culling Type set to "Always Animate". I do this all the time, sometimes in mass quantities (i.e. dozens of skinned animated meshes hidden off camera) and have never seen any performance impact.
     
  3. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    Thanks!! I looked through the code again looking for something that would cause it to be expensive, after knowing that it shouldn't be, and saw that a component was running animation on the objects when they were off-screen, causing the big animation.update costs I was seeing. I just assumed that I had a lot more (30) characters in the off-screen pool than what was viable for this solution.

    Now I run an Animation.Stop() on them and the cost is back down to nothing'ish.

    I still get the animation.rebuildinternalstate spike the first time they spawn, though not on the subsequent times, even though their animations are stopped every time they die. I seems like there's something that's not initialized in the start if the animations are stopped.

    I tried forcing an animation to play before stopping it when they are instantiated. Thinking maybe it would be forced to initialize then, but it didn't help. I did them right after one another though, maybe that doesn't work?

    Like this:
    For(Instantiate All the Things!)
    Thing = MakeaThing!()
    Thing.Animation.Play("someanimation");
    Thing.Animation.Stop();

    Did I make sense? Any idea how to get around that first chug?
     
    Last edited: Feb 16, 2012
  4. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    Avoiding stopping the animations manually with Animation.Stop(), and just using Based on User bounds like I tried now, seems to behave the same way. Something happens the first time it comes into play, and it's giving me an Animation.RebuildInternalState spike of 70'ish ms for each character. It does not give the ms spike if the object has already been in play. Would love to have a way to force this spike to wrap up during the scene load, but haven't found one so far.
     
  5. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    Still getting the spikes first times an enemy gameobject is put into play. Not sure how to go about this. I found a unity guy called _Rej_ who tweeted 6 months ago:
    "but at least I squashed several nasty loading and animations spikes today ;) At last my revenge on Animation.RebuildInternalState !"

    Does he hang around here? Because that tweet sounds promising! :D
     
  6. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    I appear to have come full circle back to the Animation.RebuildInteralState spike again. Currently it's taking 20ms for each monster popping into the game, causing a notciable frame-pop in the game. Thought I'd check in if anyone has had any encounters with this.
     
  7. Zaibatsu

    Zaibatsu

    Joined:
    Dec 23, 2010
    Posts:
    10
    still nothing?
     
  8. Bovine

    Bovine

    Joined:
    Oct 13, 2010
    Posts:
    195
    What if it's on screen but the mesh is invisible?

    We have a LOD system that we created pre unity LOD that hides skinned animations, sorting skinned animations relative to the player's camera, we are turning the animations on and off but we do this potentially many times a frame? I suppose an alternative would be to move the skinned mesh behind the camera when it isn't to be rendered...?
     
  9. Bovine

    Bovine

    Joined:
    Oct 13, 2010
    Posts:
    195
    FutureRobot, I see the same tweet but have no idea how he managed it... maybe I'll mention him in a tweet and see if he bites
     
  10. Tinus

    Tinus

    Joined:
    Apr 6, 2009
    Posts:
    437
    I ran into this issue last week, and I think I figured out a succesful workaround for my specific case.

    I'm using PoolManager to spawn enemies that have animation components on them, and same as for Futurerobot, they caused lagspikes each time PoolManager spawned them.

    After reading here that switching the gameObject between Enabled/Disabled states brought me to replace all of PoolManager's internal calls to SetActiveRecursively with this custom function:

    Code (csharp):
    1.  
    2. static Vector3 inactivePosition = new Vector3(0f, -100f, -100f);
    3.  
    4. internal bool DespawnInstance(Transform xform) // Existing method in SpawnPool.cs
    5.     {
    6.        ...
    7.  
    8.         // Deactivate the instance and all children
    9.         //xform.gameObject.SetActiveRecursively(false);
    10.  
    11.         // Deactivate the all child components, except for animations
    12.         SetTransformActiveRecursively(xform, false);
    13.  
    14.         xform.position = inactivePosition;
    15.  
    16.         ...
    17.     }
    18.  
    19. void SetTransformActiveRecursively(Transform transform, bool active)
    20.     {
    21.         SetTransformActive(transform, active);
    22.         if (transform.childCount > 0)
    23.         {
    24.             for (int i = 0; i < transform.childCount; i++)
    25.                 SetTransformActiveRecursively(transform.GetChild(i), active);
    26.         }
    27.     }
    28.  
    29.     // Flags used to find the 'enabled' property on Unity components that don't expose it.
    30.     const System.Reflection.BindingFlags flags =
    31.             System.Reflection.BindingFlags.GetField |
    32.             System.Reflection.BindingFlags.SetField |
    33.             System.Reflection.BindingFlags.Public |
    34.             System.Reflection.BindingFlags.NonPublic |
    35.             System.Reflection.BindingFlags.Static |
    36.             System.Reflection.BindingFlags.Instance;
    37.  
    38. void SetTransformActive(Transform transform, bool active)
    39.     {
    40.         Animation[] animations = transform .GetComponents <Animation >();
    41.         if (animations .Length > 0)
    42.         {
    43.             Animation anim;
    44.             for ( int i = 0; i < animations .Length; i ++)
    45.             {
    46.                 anim = animations[i];
    47.                 if (active)
    48.                     anim .Sample();
    49.                 else
    50.                     anim .Stop();
    51.             }
    52.  
    53.             Component[] components = transform.GetComponents< Component> ();
    54.             Component component;
    55.             System .Type type;
    56.             System .Reflection .PropertyInfo property;
    57.             for ( int j = 0; j < components .Length; j ++)
    58.             {
    59.                 component = components[j];
    60.                 type = component .GetType();
    61.                 if ( !(type == typeof( Animation)) )))
    62.                 {
    63.                     property = type .GetProperty( "enabled", flags);
    64.                     if (property != null)
    65.                         property .SetValue(component, active, null);
    66.                 }
    67.             }
    68.         }
    69.         else
    70.             transform .gameObject .active = active;
    71.     }
    This treats gameObjects with an Animation component on them differently. It doesn't enable/disable those objects, instead just starting/stopping animation, and individually disabling components that have the 'enable' flag. Calling Animation.Sample forces the RebuildInternalState call, and since the SetActiveRecursively method is triggered at startup for each preloaded prefab, the overhead of the call is moved to startup as well.

    Oh, and Unity's inheritance hierarchy for it's own components (SkinnedMeshRenderer, Animation) is weird. They don't inherit from a baseclass that publically exposes the 'enabled' flag, which forces this bit of code to use reflection.


    Edit: Oops, I forgot to include the actual recursive fuction, definition of the binding flags used, and the bit that moves inactive objects to some offscreen point.
     
    Last edited: Jul 10, 2012
    SweatyChair likes this.
  11. Bovine

    Bovine

    Joined:
    Oct 13, 2010
    Posts:
    195
    Oops, wrote the whole post and then clicked the wrong button and lost it all.... let's try that again:

    I solved this by:

    * Disabling the skinned mesh renderer instead of the animation - updating the animation is probably reasonably cheap and may be skipped if the mesh isn't visible.
    * To avoid a spike in RebuildInternalState() when the NPC first came into view I am moving the NPC into the camera view for a few frames while the 'Loading' screen is occluding the level and with the game paused.

    Interestingly our LOD system for skinned meshes used to use SetActiveRecursively() but we could be calling this 60 times a frame and it is expensive. For this reason we moved to enabling/disabling things: much cheaper, but this may not be an issue for you and it's easy to write a script around it to, if the object hierarchy stays the same after the object has awakened.

    Interesting what you say about Sample() as that might be better than my hacky moving of NPCs at startup.

    Cheers
    Bovine
     
  12. Tinus

    Tinus

    Joined:
    Apr 6, 2009
    Posts:
    437
    Thanks for the different perspective, Bovine.

    I might try disabling the SkinnedMeshRenderers, that might work as well. And caching a list of components to enable/disable instead of walking through the hierarchy is definitely a good optimization for specific cases. In my case though, spawning certainly doesn't happen multiple times every frame, so I went for a reusable, general fix.

    And yes, as far as I can tell calling Animation.Sample() forces the update, which I guess is preferable to moving objects into view for a couple of frames. Please let us know if it works for you. :)
     
    Last edited: Jul 11, 2012
  13. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    That's interesting stuff! I'll have a look at this again when I'm optimizing next.

    I think in the end I didn't actually disable anything, just set the brain to an inactive state so that it doesn't do anything. In my spawnpool they're still technically playing their animation, but it's culled based on user bounds. I don't have the profiler in front of me right now, but I think this led to the spike only happening first time they moved into the frame, not on subsequent spawns of the same monster. It could be interesting to dump them all into view behind our loading screen at start as Bovine suggested, I'm going to try that out.
     
  14. Futurerobot

    Futurerobot

    Joined:
    Jul 13, 2011
    Posts:
    179
    Had another go at this, and finally got rid of the spike! I used Animation.Sample() like Tinus, thanks for the tip!

    As mentioned, the rebuild spike only shows up first time I spawn my enemies, not on the consecutive spikes. Because of how my spawnpool just "pacifies" the characters instead of deactivating them.

    All I had to do was add "animation.Sample()" in Start() for the animated objects and the ms spike on spawn is gone, the cost happening on load as it should.
     
  15. DAVco

    DAVco

    Joined:
    May 27, 2013
    Posts:
    7
    Resurrecting an old thread here, but after wrestling with this problem for quite some time, I thought my findings may be of use to some people.
    What I've found is; Enabling/Disabling the GameObject will cause an Animation.RebuildInternalState spike, however disabling/enabling every component attached to that GameObject individually, while leaving the GameObject active does not cause the spike. I'd love to know what disabling the GameObject does differently - it could potentially be something to do with the transform component that gets disabled when disabling the entire GameObject? But that's just a guess.

    The first time an animated object is shown to a camera, the Animation.RebuildInternalState spike occurs, however I think that can be moved to load-time by calling Animation.Sample().
     
  16. JonPQ

    JonPQ

    Joined:
    Aug 10, 2016
    Posts:
    120
    Resurrecting the dead twice? So does this work with disabled gameObjects ? I need to pre-warm the animations during loading screen.... but the gameObject Hierarchies are complex, and run various scripts if enabled, which I want to avoid. I just want to warm the animations and Animators.... possible ?

    ...aaand how do you pre-warm Animator / Mecanim animations ?
     
    Last edited: Aug 15, 2017
  17. T5Shared

    T5Shared

    Joined:
    Oct 19, 2018
    Posts:
    152
    Resurrecting the dead three times?

    Thank you DAVco, that Animation.Sample() trick worked for me. Yes, still using the Legacy Animation system in 2019! In my case, the expensive call to Animation.RebuildInternalState() was done on a certain frame while rendering the shadow map (an animated animal with an extravagant amount of bones was visible in the shadow map for the first time). The Unity Profiler indicated a 1 ms duration for Animation.Sample(). After calling Animation.Sample() when creating the animal, the duration changed to 0.1 ms, because RebuildInternalState() was no longer called. Nice!