Advertisement
cajphrase

Articulator (Burst Animator)

Jul 8th, 2025
347
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.09 KB | None | 0 0
  1. public class ArticulatorManager : Singleton<ArticulatorManager>
  2. {
  3.     public void LateUpdate()
  4.     {
  5.         ArticulatorSystem.Update();
  6.     }
  7.  
  8.     [BurstCompile]
  9.     public struct UpdateJob : IJobParallelFor
  10.     {
  11.         public UnmanagedList<ArticulatorData> activeArticulators;
  12.  
  13.         public void Execute(int i)
  14.         {
  15.             activeArticulators[i].Execute();
  16.         }
  17.     }
  18.  
  19.     public void OnEnable()
  20.     {
  21.         activeArticulators = new(32, Allocator.Persistent);
  22.     }
  23.  
  24.     public void OnDisable()
  25.     {
  26.         if (activeArticulators.IsCreated)
  27.         {
  28.             activeArticulators.Dispose();
  29.             activeArticulators = default;
  30.         }
  31.     }
  32.  
  33.  
  34. }
  35.  
  36. public class Articulator : SBPMonoBehaviour
  37. {
  38.     [GetComponentsInChildren]
  39.     public IArticulationJobProvider[] jobProviders;
  40.  
  41.     public void OnEnable() => ArticulatorManager.Register(this);
  42.     public void OnDisable() => ArticulatorManager.Unregister(this);
  43.  
  44.  
  45.     private List<Transform> boundTransforms = new();
  46.     public ArticulatorChildHandle Bind(Transform transform)
  47.     {
  48.         var i = boundTransforms.IndexOf(transform);
  49.         if (i < 0)
  50.         {
  51.             boundTransforms.Add(transform);
  52.             i = boundTransforms.Length - 1;
  53.         }
  54.         return new(GetInstanceID(), i);
  55.     }
  56. }
  57.  
  58. public struct ArticulatorChildHandle
  59. {
  60.     public InstanceID articulatorId;
  61.     public int childIndex;
  62. }
  63.  
  64. public interface IArticulationJob : IDisposable
  65. {
  66.     public void Execute(ref ArticulationHeirachy articulator);
  67. }
  68.  
  69. // to be put on a monobehaviour, will have transform references and stuff.
  70. public interface IArticulationJobProvider<T> where T : IArticulationJob
  71. {
  72.     public ArticulatorJobData GetJobData(Articulator articulator);
  73. }
  74.  
  75.  
  76. public struct ArticulatorJobData : IDisposable
  77. {
  78.     public void* data; // these pointers will go all over the place; DoD says we should use aligned arrays
  79.     public FunctionPointer<ArticulationJobDel> func;
  80.     public FunctionPointer<BurstDelegate> dispose;
  81.  
  82.     public static ArticulatorJobData Create<T>(T job) where T : unmanaged, IArticulationJob
  83.     {
  84.         var ptr = Ptr.Create(job, Allocator.Persistent);
  85.         var func = Gen.Get<T>();
  86.         return new(ptr, func);
  87.     }
  88.  
  89.     public void Execute(ref ArticulationHeirachy articulator)
  90.     {
  91.         func.Invoke(ref articulator, data);
  92.     }
  93.  
  94.     public void Dispose()
  95.     {
  96.         dispose.Invoke(data);
  97.         Ptr.FreeTracked(data, Allocator.Persistent);
  98.     }
  99. }
  100.  
  101.  
  102. public class TestArticulation : MonoBehaviour, IArticulationJobProvider
  103. {
  104.     public Transform root;
  105.     public Transform tip;
  106.  
  107.     public ArticulatorJobData GetJobData(Articulator articulator)
  108.     {
  109.         var job = new TestArticulationJob()
  110.         {
  111.             bones = AnimationUtility.ExtractChain(root, tip)
  112.                 .Select(x => articulator.Bind(x))
  113.                 .ToNativeArray(Allocator.Persistent);
  114.         }
  115.         return ArticulatorJobData.Create(job);
  116.     }
  117.  
  118.     [BurstCompile]
  119.     public struct TestArticulationJob : IArticulationJob
  120.     {
  121.         public NativeArray<ArticulatorChildHandle> bones;
  122.  
  123.         public void Execute(ref ArticulationHeirachy articulator)
  124.         {
  125.             for (int i = 0; i < bones.Length; i++)
  126.             {
  127.                 ref var bone = ref articulator.RefChildTransform(bones[i]);
  128.                 bone.position += 1;
  129.             }
  130.         }
  131.  
  132.         public void Dispose()
  133.         {
  134.             bones.Dispose();
  135.         }
  136.     }
  137. }
  138.  
  139.  
  140. public delegate void ArticulationJobDel(ref ArticulationHeirachy art, void* jobData);
  141. public delegate void BurstDelegate(void* data);
  142.  
  143. // to be codegenned, create a delegate based on all IArticulationJob types.
  144. [BurstCompile]
  145. public static class Gen
  146. {
  147.     public static FunctionPointer<ArticulationJobDel> Get<T>()
  148.     {
  149.         return T switch
  150.         {
  151.             TestArticulation => BurstCompiler.CompileFunctionPointer<ArticulationJobDel>(TestArticulationDel),
  152.         }
  153.     }
  154.     [BurstCompile, MonoPInvokeCallback(typeof(ArticulationJobDel))]
  155.     public static void TestArticulationDel(ref ArticulationHeirachy a, void* data) => UnsafeUtility.AsRef<TestArticulation>(data).Execute(ref a);
  156. }
  157.  
  158. public struct InstanceID
  159. {
  160.     public int Value;
  161. }
  162.  
  163. [InitializeSharedStatic]
  164. public static class ArticulatorSystem
  165. {
  166.     private static UnmanagedList<ArticulationHeirachy> activeArticulators;
  167.     private static UnmanagedList<InstanceID> instanceIDs;
  168.     private static UnsafeParallelHashMap<InstanceID, int> articulatorMap;
  169.     private static TransformAccessArray transformAccessArray;
  170.  
  171.     public static void Update()
  172.     {
  173.  
  174.     }
  175.  
  176.     public static void Register(Articulator articulator)
  177.     {
  178.         if (!activeArticulators.IsCreated)
  179.             return;
  180.  
  181.         activeArticulators.Add(ArticulationHeirachy.Create(articulator));
  182.         var instanceId = articulator.GetInstanceID();
  183.         instanceIDs.Add(instanceId));
  184.         articulatorMap[instanceId] = activeArticulators.Length - 1;
  185.     }
  186.  
  187.     public static void Unregister(Articulator articulator)
  188.     {
  189.         if (!activeArticulators.IsCreated)
  190.             return;
  191.        
  192.         var instanceId = articulator.GetInstanceID();
  193.         var index = articulatorMap[instanceId];
  194.         activeArticulators[index].Dispose();
  195.         activeArticulators.RemoveAtSwapBack(index);
  196.         instanceIDs.RemoveAtSwapBack(index);
  197.         articulatorMap.Remove(instanceId);
  198.         articulatorMap[instanceIDs[index]] = index;
  199.     }
  200.    
  201.     // Run all jobs for each articulator on a separate thread
  202.     [BurstCompile]
  203.     public struct UpdateArticulatorJob : IJobParallelFor
  204.     {
  205.         public UnmanagedList<ArticulationHeirachy> articulators;
  206.  
  207.         public void Execute(int i)
  208.         {
  209.             articulators[i].RunJobs();
  210.         }
  211.     }
  212.  
  213.  
  214.  
  215.     [BurstCompile]
  216.     public struct UpdateArticulatorJob : IJobParallelForTransform
  217.     {
  218.         // the hashmap lookups are a bit wasteful because we have to do it each time for every child. this is almost certainly still faster than scheduling one job per articulator.
  219.         public UnsafeParallelHashMap<InstanceID, int> articulatorMap;
  220.         public UnmanagedList<ArticulationHeirachy> articulators;
  221.         // updating the transforms needs to be done in one big job due to how the taa works - unity will batch together
  222.         // transforms in the same heirachy into the same thread, which for us will give the effect of one thread per articulator.
  223.         // however the order is not guaranteed so we need to map back to the originator.
  224.         public UnmanagedList<ArticulatorChildHandle> transformAccessMap;
  225.        
  226.         public void Execute(int i, TransformAccess transform)
  227.         {
  228.             var n = transformAccessMap[i];
  229.             var tx = articulators[articulatorMap[n.articulatorId]].children[n.childIndex];
  230.             transform.SetPositionAndRotation(tx.position, tx.rotation);
  231.         }
  232.     }
  233.  
  234.     [BurstCompile]
  235.     public struct UpdateArticulatorJob : IJob
  236.     {
  237.         public UnmanagedList<ArticulationHeirachy> articulators;
  238.         public InstanceRenderer.Writer instanceRendererWriter;
  239.  
  240.         public void Execute()
  241.         {
  242.             for (int i = 0; i < articulators.Length; i++)
  243.             {
  244.                 ref var art = ref articulators[i];
  245.                 art.AddInstances(instanceRendererWriter);
  246.             }
  247.         }
  248.     }
  249. }
  250.  
  251.  
  252. public struct ArticulationHeirachy
  253. {
  254.     // flat array of all children excluding the root - world space
  255.     // how do we ensure we have only the transforms that we care about in here? will have rather 'furry' trees if we collect everything
  256.     UnmanagedArray<EntityTransform> children;
  257.  
  258.     // cache the original state for the children so they can be reset - mostly useful for previewing animations in editor mode
  259.     UnmanagedArray<EntityTransform> initialChildrenState;
  260.    
  261.     UnmanagedArray<ArticulationJobData> jobs;
  262.    
  263.     UnmanagedArray<RenderItem> renderItems; // each child could have zero or many prefabs to render
  264.  
  265.     private struct RenderItem
  266.     {
  267.         // index of parent transform in children array
  268.         public int parent;
  269.         // prefab to render
  270.         public PrefabReference prefab;
  271.         // each prefab is not necessarily located at identity of child
  272.         public EntityTransform localTransform;
  273.     }
  274.  
  275.     public static ArticulationHeirachy Create(Articulator articulator)
  276.     {
  277.         // get all children
  278.         var allTransforms = articulator.boundTransforms;
  279.         children = new(allTransforms.Length, Allocator.Persistent);
  280.         var tempRenderItems = new UnmanagedList<RenderItem>(32, Allocator.Temp);
  281.         for (int i = 0; i < allTransforms.Length; i++)
  282.         {
  283.             children[i] = initialChildrenState[i] = allTransforms[i];
  284.            
  285.         }
  286.  
  287.         // populate jobs
  288.         var providers = articulator.jobProviders;
  289.         jobs = new(providers.Length, Allocator.Persistent);
  290.         for (int i = 0; i < providers.Length; i++)
  291.         {
  292.             var provider = providers[i];
  293.             jobs[i] = provider.GetJobData();
  294.         }
  295.     }
  296.  
  297.     public ref EntityTransform RefChildTransform(ArticulatorChildHandle handle)
  298.     {
  299.         return ref children[handle.childIndex];
  300.     }
  301.  
  302.     public void RunJobs()
  303.     {
  304.         for (int i = 0; i < jobs.Length; i++)
  305.         {
  306.             jobs[i].Invoke(ref this);
  307.         }
  308.     }
  309.  
  310.     public void ApplyTo(TransformAccessArray taa)
  311.     {
  312.         for (int i = 0; i < children.Length; i++)
  313.         {
  314.             var tx = children[i];
  315.             // nope. this is not how this works.
  316.             taa[transformToUnityMap[i]].SetPositionAndRotation(tx.position, tx.rotation);
  317.         }
  318.     }
  319.  
  320.     // idea is this is done in burst so we pass in a struct writer
  321.     public void AddInstances(InstanceRenderer.Writer instanceRendererWriter)
  322.     {
  323.         for (int i = 0; i < renderItems.Length; i++)
  324.         {
  325.             var r = renderItems[i];
  326.             instanceRendererWriter.AddInstance(r.prefab, children[r.parent] * r.localTransform);
  327.         }
  328.     }
  329. }
  330.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement