Nirvan
February 24, 2023, 10:35am
1
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:
[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
:
*ParentFollowChain_Jobs.cs
:
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 .