Obi Official Forum

Full Version: Schedule Cloth Jobs in Update and Complete in Late Update
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hey I've written a custom Cloth Update script, it works perfectly fine, but it takes around 6ms to complete in the current frame....

Is there a way to schedule the jobs in Update and Complete it in Late Update to minimize the waiting time?
(11-02-2022, 02:07 PM)flow____ Wrote: [ -> ]Hey I've written a custom Cloth Update script, it works perfectly fine, but it takes around 6ms to complete in the current frame....

Is there a way to schedule the jobs in Update and Complete it in Late Update to minimize the waiting time?

Hi!

You should be able to store a reference to the JobHandle returned by the solvers, and call Complete() in LateUpdate(). Note however that this will just kick the jobs in LateUpdate(), (scheduling a job does not guarantee it will begin execution immediately) so there’s a pretty big chance you will gain nothing performance wise.

When using the Burst backend, you can call ScheduleBatchedJobs() to force any scheduled jobs to start executing even if Complete() hasn’t been called yet.
(11-02-2022, 04:15 PM)josemendez Wrote: [ -> ]Hi!

You should be able to store a reference to the JobHandle returned by the solvers, and call Complete() in LateUpdate(). Note however that this will just kick the jobs in LateUpdate(), (scheduling a job does not guarantee it will begin execution immediately) so there’s a pretty big chance you will gain nothing performance wise.

When using the Burst backend, you can call ScheduleBatchedJobs() to force any scheduled jobs to start executing even if Complete() hasn’t been called yet.


Yes, it did really improve my FPS from +30 FPS up on my benchmark, I'm also noticed I don't need to render the clothes when the visuals don't update, my clothes physics are not necessary game play elements, so I don't need to update it when there is no render frame, so I just called  
Quote:if (OnDemandRendering.willCurrentFrameRender == false) return;

which skips the calculation if there is not a camera render frame.

If someone stuck with this problem, my notes are you need to call the A phase where you usually have updated all transforms of the character, and the B phase probably after this point maybe in OnPreRenderFrame of the camera.

My code looks like this: A bit messy, but it does its job really well:
Code:
using System.Collections.Generic;
using Obi;
using Unity.Burst;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;

namespace StoneReverieGames.Controller
{
    [BurstCompile]
    public class SR_Cloth_Updater : ObiUpdater
    {
        //[SerializeField] protected int substeps = 4;
        private SR_Motor _motor;

        private float accumulatedTime;

        protected virtual void OnEnable()
        {
            accumulatedTime = 0f;
            writeThisFrameAlready = false;

            if (_motor == null)
            {
                _motor = this.GetComponent<SR_Motor>();
            }
            if (_motor != null)
            {
                _motor.OnPostVisuals -= UpdateClothA;
                _motor.OnPostVisuals += UpdateClothA;

                RenderPipelineManager.beginContextRendering -= UpdateCloth_B_OnCamera;
                RenderPipelineManager.beginContextRendering += UpdateCloth_B_OnCamera;
            }
        }

        protected virtual void OnDisable()
        {
            if (_motor != null)
            {
                _motor.OnPostVisuals -= UpdateClothA;

                RenderPipelineManager.beginContextRendering -= UpdateCloth_B_OnCamera;
            }
        }

        private float _previousDeltaTime;
        private float _currentRenderFrameDeltaTime;

        private bool writeThisFrameAlready;


        protected virtual void UpdateClothA(float _deltaTime)
        {
            if (_motor != null && _motor._currentLOD != SR_Motor.LOD.LEVEL_0 || (solvers.Count == 0 || solvers.Count != 0 && solvers[0].enabled == false || this.enabled == false)) return;
            if (_deltaTime <= 0f) return;

            _currentRenderFrameDeltaTime += _deltaTime;

            if (OnDemandRendering.willCurrentFrameRender == false) { return; }
            if (writeThisFrameAlready) return;

            writeThisFrameAlready = true;

            PrepareFrame();

            BeginStepA(_currentRenderFrameDeltaTime);

            SubstepA(_currentRenderFrameDeltaTime, _currentRenderFrameDeltaTime, 1);

            JobHandle.ScheduleBatchedJobs();
        }


        protected virtual void UpdateCloth_B_OnCamera(ScriptableRenderContext context, List<Camera> cameras)
        {
            if (writeThisFrameAlready == true)
                UpdateClothB(_currentRenderFrameDeltaTime);
        }

        protected virtual void UpdateClothB(float _deltaTime)
        {
            if (writeThisFrameAlready == false) return;
            if (_deltaTime <= 0f) return;

            writeThisFrameAlready = false;

            BeginStepB(_currentRenderFrameDeltaTime);

            SubstepB(_currentRenderFrameDeltaTime, _currentRenderFrameDeltaTime, 1);

            EndStep(_currentRenderFrameDeltaTime);

            accumulatedTime += _previousDeltaTime;
            _previousDeltaTime = _currentRenderFrameDeltaTime;

            Interpolate(_currentRenderFrameDeltaTime, accumulatedTime);

            accumulatedTime -= _currentRenderFrameDeltaTime;

            _currentRenderFrameDeltaTime = 0f;
        }

        List<IObiJobHandle> BeginStepHandles = new List<IObiJobHandle>();

        protected void BeginStepA(float stepDeltaTime)
        {
            // Update colliders right before collision detection:
            ObiColliderWorld.GetInstance().UpdateColliders();
            ObiColliderWorld.GetInstance().UpdateRigidbodies(solvers, stepDeltaTime);
            ObiColliderWorld.GetInstance().UpdateWorld(stepDeltaTime);

            // Kick off all solver jobs:
            foreach (ObiSolver solver in solvers)
                if (solver != null)
                    BeginStepHandles.Add(solver.BeginStep(stepDeltaTime));
        }

        protected void BeginStepB(float stepDeltaTime)
        {
            // wait for all solver jobs to complete:
            foreach (IObiJobHandle handle in BeginStepHandles)
                if (handle != null)
                    handle.Complete();

            BeginStepHandles.Clear();
        }

        List<IObiJobHandle> SubstepHandles = new List<IObiJobHandle>();

        protected void SubstepA(float stepDeltaTime, float substepDeltaTime, int index)
        {

            // Kick off all solver jobs:
            foreach (ObiSolver solver in solvers)
                if (solver != null)
                    SubstepHandles.Add(solver.Substep(stepDeltaTime, substepDeltaTime, index));
        }

        protected void SubstepB(float stepDeltaTime, float substepDeltaTime, int index)
        {
            // wait for all solver jobs to complete:
            foreach (IObiJobHandle handle in SubstepHandles)
                if (handle != null)
                    handle.Complete();


            SubstepHandles.Clear();
        }
    }
}