Why does FixedRateCatchUpManager run faster than desired time?

I created a custom SystemGroup for the background simulation of my game. I set the Rate manager to
RateManager = new RateUtils.FixedRateCatchUpManager(0.2f); which as I understand it means the system should run every 0.2 seconds/5 times a second. But when I run the system with a single entity it seemingly running once per frame rather than based on a time interval. I’m not sure what’s going wrong as everything I’ve read online has said it should be as simple as making the custom system group, setting it’s rate manager and making sure than any system you want to run at that rate is put in that system group, which I double checked both in my code and in the systems window in the editor and it’s definitely in the right system group.

System Group:

[UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
    public partial class BackgroundSimulationSystemGroup : ComponentSystemGroup {
        public void OnCreate(ref SystemState state) {
            RateManager = new RateUtils.FixedRateCatchUpManager(0.2f);
        }
    }

System:

[UpdateInGroup(typeof(SystemGroups.BackgroundSimulationSystemGroup))]
    public partial struct UpdateNavPaths : ISystem {
        [BurstCompile]
        public void OnCreate(ref SystemState state) {
            state.RequireForUpdate<Navigation.Components.Actor>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state) {
            JobHandle handle = new Navigation.Jobs.UpdatePathsJob
            {
                TargetPosition = SystemAPI.GetComponentLookup<LocalToWorld>(),
            }.ScheduleParallel(state.Dependency);

            handle.Complete();
        }

Job:

[BurstCompile]
    public partial struct UpdatePathsJob : IJobEntity {
        [ReadOnly] public ComponentLookup<LocalToWorld> TargetPosition;
       
        private void Execute(Aspects.Actor actor) {
            if (!TargetPosition.HasComponent(actor.Target))
                return;
            UnityEngine.Debug.Log($"{TargetPosition.GetRefRO(actor.Target).ValueRO.Position}");
        }
    }

Any input on what might be the issue would be apprecaited.

The signature “void OnXXX(ref SystemState)” is only for ISystem. ComponentSystemGroup is a managed system type that derives from ComponentSystemBase. You need to follow the semantics of implementing the system callbacks, which is to override the virtual methods, in this case:

    [UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
    public partial class BackgroundSimulationSystemGroup : ComponentSystemGroup
    {
        public void OnCreate(ref SystemState state)
        {
            // call base method as a best practice when overriding a non-abstract virtual method
            // in the case of ComponentSystemGroup there's some necessary internal set up requiring you to call this
            base.OnCreate();
            RateManager = new RateUtils.FixedRateCatchUpManager(0.2f);
        }
    }

https://docs.unity3d.com/Packages/com.unity.entities@1.0/api/Unity.Entities.ComponentSystemBase.OnCreate.html#Unity_Entities_ComponentSystemBase_OnCreate

Given that FixedStepSimulationSystemGroup itself sets up its rate manager in its constructor, you could also follow suit if you have an exact update timestep in mind.
https://github.com/needle-mirror/com.unity.entities/blob/0783fbd7a95eaa8cc0e425d3925760ec9564ab0a/Unity.Entities/DefaultWorld.cs#L349
Also, as shown in that code, you may consider using ComponentSystemGroup.SetRateManagerCreateAllocator which both sets the rate manager and creates allocators which are safe to use from systems within the group regardless of how often the group updates relative to the frame rate. You get a pair of allocators that swap for every time the group runs, accessible from SystemState.WorldUpdateAllocator.

    [UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
    public partial class BackgroundSimulationSystemGroup : ComponentSystemGroup
    {
        public BackgroundSimulationSystemGroup()
        {
            SetRateManagerCreateAllocator(new RateUtils.FixedRateCatchUpManager(0.2f));
        }
    }

Just remember if you are overriding OnCreate in ComponentSystemGroup you call base.OnCreate()

Thanks for the assistance. Can I assume that if I don’t have a custom constructor that calls SetRateManagerCreateAllocator that allocators don’t work? Or don’t work as intented? What sort of side effects would it create if I didn’t call that function?

| Just remember if you are overriding OnCreate in ComponentSystemGroup you call base.OnCreate()
I’ve made the change, although I find it odd that you have to manually call the base class function instead of it being architected to internall call all the required functions first and then call the overriden OnCreate

The choice won’t affect anything if you never use SystemState.WorldUpdateAllocator. If you just use the normal global allocators (Domain, Persistent, TempJob, Temp, etc.) you won’t see any problems. Of course, for JobTemp you still want to make sure the jobs complete within 4 frames for the lifespan management of those allocations.

Not setting the allocator will mean accessing SystemState.WorldUpdateAllocator will end up using the allocators that are available from within SimulationSystemGroup. I’m not sure if there are mechanisms to prevent / stall the allocator rollback and swap that happens for the pair of allocators that the group uses if there’s a job running that uses the allocator. If the mechanism exists, it would be safe to use the existing allocators, but the main thread would have to wait for those jobs to finish before the group can swap the allocators, and that would be a long wait for long jobs. If no such mechanism exists, the allocators could be invalidated during long job execution which would mean you have pointers to invalid memory in the job. Both results are pretty undesirable. If the jobs are short enough to last one real frame / end up getting completed one way or another (e.g. used as a dependency for something else in SimulationSystemGroup), there shouldn’t be a problem.

If you do create the allocators for your group, you’ll have general safety of allocations as long as the jobs each complete every group cycle. Technically there’s more time, until the group end point after the next time the system in question runs, but for systems that run every group update, you wouldn’t want average execution time greater than one group cycle. There also is a downside in that allocations would be held for the duration of two group update cycles, so if you make a bunch of allocations that are only necessary for some short jobs, TempJob would make more sense.

The world update / system group allocators are pretty neat in general. There’s a pair of world update allocators for the whole world and other allocator pairs that are defined by system groups down the execution hierarchy (with SetRateManagerCreateAllocator) which will override what SystemState.WorldUpdateAllocator returns. You don’t technically need to dispose allocations from them, much like Temp allocations, so for the most part the stuff is pretty friendly to use.
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/allocators-world-update.html
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/allocators-system-group.html

The OnCreate stuff is typical OOP stuff, you often see it being necessary to do this kind of base implementation calling. I wouldn’t call that out of the ordinary.