Trying to convert procedural animation monobehaviour to jobs - cant get it right

Hello,

Two days ago I took another attempt for using jobs but I met brick wall and can’t solve a problem.
I wrote simplified 100 line code for follow animation in which I require access to some parent coords.
I tried writing jobs code for it but it gets crazy and works different per few playmode launches.

Here how it looks:

205043-jobs-trouble.gif

[205042-jobstests.zip|205042] (Save link as file)

Mono code only: using System.Collections.Generic;using UnityEngine; public class ParentFol - Pastebin.com

Jobified component only: using System;using System.Collections.Generic;using Unity.Burst;using Unit - Pastebin.com

Can someone help me out why it’s happening?

How can I solve it, do things more performant/easier to read code.

I guess the problem lies in accessing index-1 data causing some desync.

I tried making separate approach with parent positions/rotations ‘buffer’ but no success (it’s code is included in the zip)

ParentFollowChainMono.cs

using UnityEngine;

public class ParentFollowChainMono : MonoBehaviour
{

    [UnityEngine.Serialization.FormerlySerializedAs("Chain")]
    [SerializeField] Transform[] _chain;

    Vector3[] _initialLocalPos;
    Quaternion[] _initialLocalRot;
    Vector3[] _animatedPos;
    Quaternion[] _animatedRot;

    void Start()
    {
        int chainLen = _chain.Length;

        _initialLocalPos = new Vector3[chainLen];
        _initialLocalRot = new Quaternion[chainLen];
        _animatedPos = new Vector3[chainLen];
        _animatedRot = new Quaternion[chainLen];

        for (int i = 0; i < chainLen; i++)
        {
                var t = _chain*;*

            _initialLocalPos *= t.localPosition;*
            _initialLocalRot *= t.localRotation;*

            _animatedPos *= t.position;*
            _animatedRot *= t.rotation;*
        }
    }

    void Update()
    {
        float time = Time.time;
        float deltaTime = Time.deltaTime;

        for (int i = 0; i < _chain.Length; i++)
        {
            var chain = _chain;
            Vector3 initialLocalPos = _initialLocalPos;
            Quaternion initialLocalRot = _initialLocalRot;

            //JRestore(i);
            chain.SetLocalPositionAndRotation(initialLocalPos, initialLocalRot);

            if (i != 0)//JCompute(i);
            {
                Vector3 animPos = _animatedPos*;*
                {
                    Vector3 frontPos = _animatedPos[i - 1] + _chain[i - 1].rotation * initialLocalPos;// Transform to relevant front of parent
                    Vector3 motionOffset = frontPos - animPos;
                    animPos += motionOffset * (30f * deltaTime);
                }
                _animatedPos *= animPos;*

                Quaternion parentRot = _animatedRot[i - 1];
                {
                    parentRot = Quaternion.FromToRotation(parentRot * initialLocalPos, animPos - _animatedPos[i - 1]) * parentRot;
                }
                _animatedRot[i - 1] = parentRot;
            }
            else//JAnimateRoot(i);
            {
                _animatedPos = chain.position + new Vector3(Mathf.Sin(time * 5f) * 0.3f, 0f, 0f);
                _animatedRot = chain.rotation;
            }

            //JApply(i);
            chain.SetPositionAndRotation(_animatedPos, _animatedRot);
        }
    }
}

ParentFollowChain_Jobs.cs

using UnityEngine;
using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Jobs;
 
public class ParentFollowChain_Jobs : MonoBehaviour
{
 
 	[UnityEngine.Serialization.FormerlySerializedAs("Chain")]
	[SerializeField] Transform[] _chain;
	TransformAccessArray _chainDataAccess;
 
	NativeArray<float3> _initialLocalPos, _animatedPos;
	NativeArray<quaternion> _initialLocalRot, _animatedRot;
 	public JobHandle Dependency;
 
 	void OnEnable()
 	{
		int chainLen = _chain.Length;
 
		_initialLocalPos = new NativeArray<float3>(chainLen, Allocator.Persistent);
		_initialLocalRot = new NativeArray<quaternion>(chainLen, Allocator.Persistent);
		_animatedPos = new NativeArray<float3>(chainLen, Allocator.Persistent);
		_animatedRot = new NativeArray<quaternion>(chainLen, Allocator.Persistent);
		_chainDataAccess = new TransformAccessArray(_chain);
 
 		for (int i = 0; i < chainLen; i++)
 		{
 			var t = _chainDataAccess*;
 
 			_initialLocalPos *= t.localPosition;
 			_initialLocalRot *= t.localRotation;
 
 			_animatedPos *= t.position;
 			_animatedRot *= t.rotation;
 		}
 	}
 
 	void OnDisable()
 	{
 		Dependency.Complete();
 
		if(_initialLocalPos.IsCreated) _initialLocalPos.Dispose();
		if(_initialLocalRot.IsCreated) _initialLocalRot.Dispose();
		if(_animatedPos.IsCreated) _animatedPos.Dispose();
		if(_animatedRot.IsCreated) _animatedRot.Dispose();
 
		if (_chainDataAccess.isCreated) _chainDataAccess.Dispose();
 	}
 
 	void Update()
 	{
 		Dependency.Complete();
 
 		Dependency = new MyJob
 		{
			InitialLocalPos = _initialLocalPos,
			InitialLocalRot = _initialLocalRot,
			AnimatedPos = _animatedPos,
			AnimatedRot = _animatedRot,
 			AnimationTime = Time.time,
 			AnimationDeltaTime = Time.deltaTime,
		}.Schedule(_chainDataAccess, Dependency);
 	}
 
 	[Unity.Burst.BurstCompile]
 	struct MyJob : IJobParallelForTransform
 	{
 		[ReadOnly] public NativeArray<float3> InitialLocalPos;
 		[ReadOnly] public NativeArray<quaternion> InitialLocalRot;
 		[NativeDisableParallelForRestriction] public NativeArray<float3> AnimatedPos;
 		[NativeDisableParallelForRestriction] public NativeArray<quaternion> AnimatedRot;
 		public float AnimationTime, AnimationDeltaTime;
 
 		void IJobParallelForTransform.Execute(int index, TransformAccess transform)
 		{
 			//JRestore(i);
 			float3 initialLocalPos = InitialLocalPos[index];
 			quaternion initialLocalRot = InitialLocalRot[index];
 			transform.SetLocalPositionAndRotation(initialLocalPos, initialLocalRot);
 
 			if (index != 0)//JCompute(i);
 			{
 				float3 parentPos = AnimatedPos[index - 1];
 				quaternion parentRot = AnimatedRot[index - 1];
 
 				float3 animPos = AnimatedPos[index];
 				{
 					float3 frontPos = parentPos + math.mul(parentRot, initialLocalPos);// Transform to relevant front of parent
 					float3 motionOffset = frontPos - animPos;
					animPos += motionOffset * (30f * AnimationDeltaTime);
 				}
 				AnimatedPos[index] = animPos;
 
 				{
					parentRot = Quaternion.FromToRotation(math.mul(parentRot, initialLocalPos), animPos - parentPos) * parentRot;
 				}
 				AnimatedRot[index - 1] = parentRot;
 			}
 			else//JAnimateRoot(i);
 			{
				AnimatedPos[index] = (float3)transform.position + new float3(math.sin(AnimationTime * 5f) * 0.3f, 0f, 0f);
 				AnimatedRot[index] = transform.rotation;
 			}
 
 			//JApply(i);
 			transform.SetPositionAndRotation(AnimatedPos[index], AnimatedRot[index]);
 		}
 	} 
}

Performance comparison:

ParentFollowChainMono.cs:

profiler mono

*ParentFollowChain_Jobs.cs:

profiler jobs

My conclusion:

Jobified version is hardly/not worth the complexity it introduced for a chain of length 5.

Side note: You don’t need to avoid using static functions like Mathf.___ or Quaternion.___ in Burst-compiled code as these will end up being inlined and optimized just fine.