Search Unity

[SOLVED] PlayerLoop.FixedUpdate - JobComponentSystem

Discussion in 'Entity Component System' started by sstrong, May 1, 2019.

  1. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,251
    How can I get IJobForEach job run on the same time as FixedUpdate()?

    The following code doesn't seem to work with PlayerLoop.Fixedupdate.

    Entities 0.0.12 preview.30 and U2019.1.0f2.

    Code (CSharp):
    1. // Raises an error
    2. //[UpdateAfter(typeof(UnityEngine.Experimental.PlayerLoop.FixedUpdate))]
    3. public class ProjectileSystem : JobComponentSystem
    4. {
    5.     #region Private Static Variables
    6.     private static EntityQuery projectileEntityQuery;
    7.     private static EntityManager entityManager;
    8.     #endregion
    9.  
    10.     #region Projectile Movement Job
    11.  
    12.     /// <summary>
    13.     /// Struct for jobs processing projectile movement.
    14.     /// </summary>
    15.     [BurstCompile]
    16.     struct ProjectileJob : IJobForEach<Translation, Rotation, Projectile>
    17.     {
    18.         public float deltaTime;
    19.  
    20.         public void Execute(ref Translation position, [ReadOnly] ref Rotation rotation, ref Projectile projectile)
    21.         {
    22.             // Update our current position using our velocity and frame time
    23.             position.Value += projectile.velocity * deltaTime;
    24.  
    25.             // Update the timer so that in the OnUpdate we can destroy the entity when required.
    26.             projectile.despawnTimer += deltaTime;
    27.         }
    28.     }
    29.  
    30.     #endregion
    31.  
    32.     #region Event Methods
    33.  
    34.     protected override void OnCreate()
    35.     {
    36.         //base.OnCreate();
    37.  
    38.         // Get the current entity manager. If it doesn't exist, create one.
    39.         entityManager = World.Active.EntityManager;
    40.  
    41.         // Projectile entity query code
    42.         // Get all the entities with a Translation and Projectile component
    43.         projectileEntityQuery = GetEntityQuery(new EntityQueryDesc
    44.         {
    45.             All = new ComponentType[] { typeof(Translation), typeof(Rotation), typeof(Projectile) }
    46.         });
    47.     }
    48.  
    49.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    50.     {
    51.         // Get it once
    52.         float deltaTime = Time.deltaTime;
    53.  
    54.         // Create a new projectile job, passing in the current frame time as an argument
    55.         ProjectileJob projectileJob = new ProjectileJob() { deltaTime = deltaTime };
    56.  
    57.         // Schedule the projectile job, returning the projectile job handle
    58.         return projectileJob.Schedule(projectileEntityQuery, inputDeps);
    59.     }
    60.  
    61.     #endregion
    62. }
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
  3. Jawsarn

    Jawsarn

    Joined:
    Jan 12, 2017
    Posts:
    245
    I have the same problem and it is really _really_ annoying. It makes me not update to the newest version of Entities at this current point. Is there at least a way to use the [UpdateBefore/After] attributes to sort a list of systems if running them manually?
     
  4. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    @Jawsarn I think you can put the systems into a custom SystemGroup and call SortSystemUpdateList(). Then just manually tick the SystemGroup.

    But I know this can be super weird when SimulationSystemGroup will be in FixedUpdate again in the future.

    So if you just want everything to stay the same and just move SimulationSystemGroup to FixedUpdate, there are 2 ways to go about it (besides staying at an older version)

    A) Manual patching. Move the Entities package into your Assets folder. Delete the one in Package Manager. Patch one line in ScriptBehaviourUpdateOrder.UpdatePlayerLoop():

    Replace
    Code (CSharp):
    1. if (playerLoop.subSystemList[i].type == typeof(Update))
    to
    Code (CSharp):
    1. if (playerLoop.subSystemList[i].type == typeof(FixedUpdate))
    OR

    B) Disable DefaultWorldInitialization and roll your own. This is what I do since I need to take care of other customization like Dependency Injection. Here you can pretty much copy&paste DefaultWorldInitialization.cs. But instead of calling ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world), you roll your own UpdatePlayerLoop() with a little reflection:

    Code (CSharp):
    1. static MethodInfo insertManagerIntoSubsystemListMethod = typeof(ScriptBehaviourUpdateOrder).GetMethod("InsertManagerIntoSubsystemList", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
    2.  
    3. public static void UpdatePlayerLoop(World world) {
    4.     var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
    5.     if (ScriptBehaviourUpdateOrder.CurrentPlayerLoop.subSystemList != null)
    6.         playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
    7.     if (world != null) {
    8.         for (var i = 0; i < playerLoop.subSystemList.Length; ++i) {
    9.             int subsystemListLength = playerLoop.subSystemList[i].subSystemList.Length;
    10.             if (playerLoop.subSystemList[i].type == typeof(FixedUpdate)) {
    11.                 var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
    12.                 for (var j = 0; j < subsystemListLength; ++j)
    13.                     newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
    14.                 var mgr = world.GetOrCreateSystem<SimulationSystemGroup>();
    15.                 var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
    16.                 genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
    17.                 playerLoop.subSystemList[i].subSystemList = newSubsystemList;
    18.             } else if (playerLoop.subSystemList[i].type == typeof(Update)) {
    19.                 var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
    20.                 for (var j = 0; j < subsystemListLength; ++j)
    21.                     newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
    22.                 var mgr = world.GetOrCreateSystem<PresentationSystemGroup>();
    23.                 var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
    24.                 genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
    25.                 playerLoop.subSystemList[i].subSystemList = newSubsystemList;
    26.             } else if (playerLoop.subSystemList[i].type == typeof(Initialization)) {
    27.                 var newSubsystemList = new PlayerLoopSystem[subsystemListLength + 1];
    28.                 for (var j = 0; j < subsystemListLength; ++j)
    29.                     newSubsystemList[j] = playerLoop.subSystemList[i].subSystemList[j];
    30.                 var mgr = world.GetOrCreateSystem<InitializationSystemGroup>();
    31.                 var genericMethod = insertManagerIntoSubsystemListMethod.MakeGenericMethod(mgr.GetType());
    32.                 genericMethod.Invoke(null, new object[] { newSubsystemList, subsystemListLength + 0, mgr });
    33.                 playerLoop.subSystemList[i].subSystemList = newSubsystemList;
    34.             }
    35.         }
    36.     }
    37.  
    38.     ScriptBehaviourUpdateOrder.SetPlayerLoop(playerLoop);
    39. }
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
  6. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,251
    Here is how I solved this:

    Code (CSharp):
    1. [DisableAutoCreation]
    2. public class ProjectileSystem : JobComponentSystem
    3. {
    4.     #region Private Static Variables
    5.     private static EntityQuery projectileEntityQuery;
    6.     private static EntityManager entityManager;
    7.     #endregion
    8.  
    9.     #region Projectile Movement Job
    10.  
    11.     /// <summary>
    12.     /// Struct for jobs processing projectile movement.
    13.     /// </summary>
    14.     [BurstCompile]
    15.     struct ProjectileJob : IJobForEach<Translation, Rotation, Projectile>
    16.     {
    17.         public float deltaTime;
    18.  
    19.         public void Execute(ref Translation position, [ReadOnly] ref Rotation rotation, ref Projectile projectile)
    20.         {
    21.             // Update our current position using our velocity and frame time
    22.             position.Value += projectile.velocity * deltaTime;
    23.  
    24.             // Update the timer so that in the OnUpdate we can destroy the entity when required.
    25.             projectile.despawnTimer += deltaTime;
    26.         }
    27.     }
    28.  
    29.     #endregion
    30.  
    31.     #region Event Methods
    32.  
    33.     protected override void OnCreate()
    34.     {
    35.         // Get the current entity manager. If it doesn't exist, create one.
    36.         entityManager = World.Active.EntityManager;
    37.  
    38.         // Projectile entity query code
    39.         // Get all the entities with a Translation and Projectile component
    40.         projectileEntityQuery = GetEntityQuery(new EntityQueryDesc
    41.         {
    42.             All = new ComponentType[] { typeof(Translation), typeof(Rotation), typeof(Projectile) }
    43.         });
    44.     }
    45.  
    46.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    47.     {
    48.         // Get it once
    49.         float deltaTime = Time.deltaTime;
    50.  
    51.         // Create a new projectile job, passing in the current frame time as an argument
    52.         ProjectileJob projectileJob = new ProjectileJob() { deltaTime = deltaTime };
    53.  
    54.         // Schedule the projectile job, returning the projectile job handle
    55.         return projectileJob.Schedule(projectileEntityQuery, inputDeps);
    56.     }
    57.  
    58.     #endregion
    59. }
    60.  
    Then add a script to a gameobject in the scene to update the system on FixedUpdate().

    Code (CSharp):
    1. public class GameController : MonoBehaviour
    2. {
    3.     #region Private Variables
    4.     private ProjectileSystem projectileSystem = null;
    5.     #endregion
    6.  
    7.     void Awake()
    8.     {
    9.       // Manually create the projectile system
    10.       projectileSystem = World.Active.GetOrCreateSystem<ProjectileSystem>();
    11.       // ...
    12.     }
    13.  
    14.     void FixedUpdate()
    15.     {
    16.         if (projectileSystem != null) { projectileSystem.Update(); }
    17.     }
    18. }
     
  7. Jawsarn

    Jawsarn

    Joined:
    Jan 12, 2017
    Posts:
    245
    Thanks for the replies! I went with Creepgin's help of using Simulation Group and base class for systems running in fixedUpdate so I can fetch them at all at the start. I guess it's not the cleanest way or best way probably, but I will most likely as patterns go refactor it at some later point.

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using Unity.Entities;
    6. using UnityEngine;
    7.  
    8. // Use this as temporary solution
    9. [DisableAutoCreation]
    10. public class FixedUpdateSystem : BaseComponentSystem
    11. {
    12.     protected override void OnUpdate()
    13.     {
    14.  
    15.     }
    16. }
    17.  
    18. [DisableAutoCreation]
    19. class FixedUpdateGroup : ComponentSystemGroup
    20. {
    21.  
    22. }
    23.  
    24. public class FixedUpdateManager : MonoBehaviour
    25. {
    26.     FixedUpdateGroup m_group;
    27.     World m_world;
    28.  
    29.     private void Start()
    30.     {
    31.         m_world = World.Active;
    32.  
    33.         m_group = m_world.CreateSystem<FixedUpdateGroup>();
    34.  
    35.         var type = typeof(FixedUpdateSystem);
    36.         var types = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.StartsWith("")).SelectMany(x => x.GetTypes()).Where(x => x.IsClass && type.IsAssignableFrom(x));
    37.  
    38.         foreach (Type t in types)
    39.         {
    40.             if (t.Name.Equals(typeof(FixedUpdateSystem).Name))
    41.             {
    42.                 continue;
    43.             }
    44.  
    45.             m_group.AddSystemToUpdateList(m_world.CreateSystem(t));
    46.         }
    47.  
    48.         m_group.SortSystemUpdateList();
    49.     }
    50.  
    51.     private void FixedUpdate()
    52.     {
    53.         m_group.Update();
    54.     }
    55. }
    56.  
    57. public class InitFixedUpdateManager : ComponentSystem
    58. {
    59.     protected override void OnCreate()
    60.     {
    61.         base.OnCreate();
    62.         var go = new GameObject();
    63.         go.AddComponent<FixedUpdateManager>();
    64.         GameObject.DontDestroyOnLoad(go);
    65.     }
    66.  
    67.     protected override void OnUpdate()
    68.     {
    69.  
    70.     }
    71. }
     
    Singtaa likes this.