Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,217 @@
using Unity.Entities;
using Unity.Mathematics;
using System.Runtime.InteropServices;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct ControllerBlob: GenericAssetBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
public string Name() => name.ToString();
public float bakingTime;
public float BakingTime() => bakingTime;
#endif
public Hash128 hash;
public Hash128 Hash() => hash;
public BlobArray<LayerBlob> layers;
public BlobArray<ParameterBlob> parameters;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum ControllerParameterType
{
Int,
Float,
Bool,
Trigger
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[StructLayout(LayoutKind.Explicit)]
public struct ParameterValue
{
[FieldOffset(0)]
public float floatValue;
[FieldOffset(0)]
public int intValue;
[FieldOffset(0)]
public bool boolValue;
public static implicit operator ParameterValue(float f) => new ParameterValue() { floatValue = f };
public static implicit operator ParameterValue(int i) => new ParameterValue() { intValue = i };
public static implicit operator ParameterValue(bool b) => new ParameterValue() { boolValue = b };
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ParameterBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public ParameterValue defaultValue;
public ControllerParameterType type;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum AnimationBlendingMode
{
Override = 0,
Additive = 1
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum AnimatorConditionMode
{
If = 1,
IfNot = 2,
Greater = 3,
Less = 4,
Equals = 6,
NotEqual = 7
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct LayerBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int defaultStateIndex;
public float initialWeight;
public int syncedLayerIndex;
// syncedTiming acts as a bool (v != 0) for layers that have syncedLayerIndex >= 0. Base layer state speed also
// need to be adjusted, so syncedTiming acts as index to synced layer for weight lerp computation
public int syncedTiming;
public AnimationBlendingMode blendingMode;
public BlobArray<StateBlob> states;
public Hash128 avatarMaskBlobHash;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct TransitionBlob
{
// Just a copy of UnityEditor.Animations.TransitionInterruptionSource
// I cannot use original enum because it is in editor assembly
public enum InterruptionSource
{
None,
Source,
Destination,
SourceThenDestination,
DestinationThenSource,
}
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public BlobArray<ConditionBlob> conditions;
public int targetStateId;
public float duration;
public float exitTime;
public float offset;
public bool hasExitTime;
public bool hasFixedDuration;
public InterruptionSource interruptionSource;
public bool orderedInterruption;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ConditionBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int paramIdx;
public ParameterValue threshold;
public AnimatorConditionMode conditionMode;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct MotionBlob
{
public enum Type
{
None,
AnimationClip,
BlendTree1D,
BlendTree2DSimpleDirectional,
BlendTree2DFreeformDirectional,
BlendTree2DFreeformCartesian,
BlendTreeDirect
}
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public Type type;
public int animationIndex;
public BlendTreeBlob blendTree;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ChildMotionBlob
{
public MotionBlob motion;
public float threshold;
public float timeScale;
public bool mirror;
public float2 position2D;
public int directBlendParameterIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct BlendTreeBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int blendParameterIndex;
public int blendParameterYIndex;
public bool normalizeBlendValues;
public BlobArray<ChildMotionBlob> motions;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct StateBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
public BlobString tag;
#endif
public uint hash;
public uint tagHash;
public float speed;
public int speedMultiplierParameterIndex;
public int timeParameterIndex;
public float cycleOffset;
public int cycleOffsetParameterIndex;
public BlobArray<TransitionBlob> transitions;
public MotionBlob motion;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ControllerAnimationsBlob
{
public BlobArray<Hash128> animations;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bb7e72e901a18aa4ea9c4806acac4167
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorControllerBlob.cs
uploadId: 897522
@@ -0,0 +1,111 @@
using Rukhanka;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
using UnityEngine;
using static Rukhanka.AnimatorControllerSystemJobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[assembly: RegisterGenericSystemType(typeof(AnimatorControllerSystem<PredictedAnimatorControllerQuery>))]
[assembly: RegisterGenericSystemType(typeof(AnimatorControllerSystem<AnimatorControllerQuery>))]
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
public partial struct AnimatorControllerSystem<T>: ISystem where T: AnimatorControllerQueryCreator, new()
{
EntityQuery animatorControllerQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var queryCreator = new T();
animatorControllerQuery = queryCreator.CreateQuery(ref ss);
ss.RequireForUpdate(animatorControllerQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var dt = SystemAPI.Time.DeltaTime;
var controllerLayersBufferHandle = SystemAPI.GetBufferTypeHandle<AnimatorControllerLayerComponent>();
var controllerParametersBufferHandle = SystemAPI.GetBufferTypeHandle<AnimatorControllerParameterComponent>();
var animatorOverrideAnimationsLookup = SystemAPI.GetComponentLookup<AnimatorOverrideAnimations>(true);
var entityTypeHandle = SystemAPI.GetEntityTypeHandle();
var controllerEventsBufferLookup = SystemAPI.GetBufferLookup<AnimatorControllerEventComponent>();
var animDBSingleton = SystemAPI.GetSingleton<BlobDatabaseSingleton>();
var internalDataSingletonQuery = SystemAPI.QueryBuilder().WithAllRW<InternalAnimatorDataSingleton>().Build();
var internalAnimatorData = FillAnimationsFromControllerSystem.GetInternalDataSingleton(internalDataSingletonQuery, ref ss);
var stateMachineProcessJob = new StateMachineProcessJob()
{
controllerLayersBufferHandle = controllerLayersBufferHandle,
controllerParametersBufferHandle = controllerParametersBufferHandle,
dt = dt,
entityTypeHandle = entityTypeHandle,
controllerEventsBufferLookup = controllerEventsBufferLookup,
animationDatabase = animDBSingleton.animations,
animatorOverrideAnimationLookup = animatorOverrideAnimationsLookup,
animatorOverrideAnimationsMap = internalAnimatorData.animatorOverrideAnimationsMap.AsParallelWriter()
};
ss.Dependency = stateMachineProcessJob.ScheduleParallel(animatorControllerQuery, ss.Dependency);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public interface AnimatorControllerQueryCreator
{
EntityQuery CreateQuery(ref SystemState ss);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct PredictedAnimatorControllerQuery: AnimatorControllerQueryCreator
{
public EntityQuery CreateQuery(ref SystemState ss)
{
var eqBuilder0 = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithAll<Simulate, PredictedGhost>()
#endif
;
var animatorControllerQuery = ss.GetEntityQuery(eqBuilder0);
return animatorControllerQuery;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerQuery: AnimatorControllerQueryCreator
{
public EntityQuery CreateQuery(ref SystemState ss)
{
var eqBuilder0 = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithNone<GhostInstance>()
#endif
;
var animatorControllerQuery = ss.GetEntityQuery(eqBuilder0);
return animatorControllerQuery;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ecdf4d0de0c58ed4489dca4b8c9ab490
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorControllerSystem.cs
uploadId: 897522
@@ -0,0 +1,89 @@
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorControllerLayerComponent: IBufferElementData, IEnableableComponent
{
public BlobAssetReference<ControllerBlob> controller;
public BlobAssetReference<ControllerAnimationsBlob> animations;
public int layerIndex;
public float weight;
public float speed;
public RuntimeAnimatorData rtd;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerParameterComponent: IBufferElementData
{
#if RUKHANKA_DEBUG_INFO
public FixedString64Bytes name;
#endif
public uint hash;
public ControllerParameterType type;
public ParameterValue value;
public float FloatValue
{
get => value.floatValue;
set => this.value.floatValue = value;
}
public int IntValue
{
get => value.intValue;
set => this.value.intValue= value;
}
public bool BoolValue
{
get => value.boolValue;
set => this.value.boolValue = value;
}
public void SetTrigger()
{
value.boolValue = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerParameterIndexTableComponent: IComponentData
{
public BlobAssetReference<PerfectHashTableBlob> value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerEventComponent : IBufferElementData, IEnableableComponent
{
public enum EventType
{
StateEnter,
StateExit,
StateUpdate
}
public EventType eventType;
public int layerId;
public int stateId;
public float timeInState;
#if RUKHANKA_DEBUG_INFO
public FixedString32Bytes stateName;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorOverrideAnimations: IComponentData, IEnableableComponent
{
public BlobAssetReference<ControllerAnimationsBlob> value;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1026c97737e562f4c816544af6ac420f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorControllerSystemComponents.cs
uploadId: 897522
@@ -0,0 +1,740 @@
using System.Runtime.CompilerServices;
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Hash128 = Unity.Entities.Hash128;
[assembly: InternalsVisibleTo("Rukhanka.Tests")]
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorControllerSystemJobs
{
[BurstCompile]
public struct StateMachineProcessJob: IJobChunk
{
public float dt;
public BufferTypeHandle<AnimatorControllerLayerComponent> controllerLayersBufferHandle;
public BufferTypeHandle<AnimatorControllerParameterComponent> controllerParametersBufferHandle;
public EntityTypeHandle entityTypeHandle;
[NativeDisableParallelForRestriction]
public BufferLookup<AnimatorControllerEventComponent> controllerEventsBufferLookup;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> animationDatabase;
[ReadOnly]
public ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup;
public NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap;
BlobAssetReference<ControllerAnimationsBlob> controllerAnimationsBlob;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var layerBuffers = chunk.GetBufferAccessor(ref controllerLayersBufferHandle);
var parameterBuffers = chunk.GetBufferAccessor(ref controllerParametersBufferHandle);
var entities = chunk.GetNativeArray(entityTypeHandle);
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
while (cee.NextEntityIndex(out var i))
{
var layers = layerBuffers[i];
var parameters = parameterBuffers.Length > 0 ? parameterBuffers[i].AsNativeArray() : default;
var e = entities[i];
DynamicBuffer<AnimatorControllerEventComponent> controllerEventsBuffer = default;
if (controllerEventsBufferLookup.HasBuffer(e) && controllerEventsBufferLookup.IsBufferEnabled(e))
controllerEventsBuffer = controllerEventsBufferLookup[e];
ExecuteSingle(layers, parameters, ref controllerEventsBuffer, e);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void ExecuteSingle
(
in DynamicBuffer<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> acpc,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
Entity entity
)
{
if (events.IsCreated)
events.Clear();
var numIntsForBitMemory = BitFieldN.CalculateUIntsCountForGivenBitCount(acpc.Length);
var triggersToResetMem = stackalloc uint[numIntsForBitMemory];
var triggersToReset = new BitFieldN(triggersToResetMem, numIntsForBitMemory);
var startIndex = 0;
for (int i = 0; i < aclc.Length; ++i)
{
ref var acc = ref aclc.ElementAt(i);
// Save controller animations blob asset reference in class variable, because passing it inside almost all functions will bloat signatures significantly
controllerAnimationsBlob = FillAnimationsFromControllerSystem.GetControllerAnimationsBlob
(entity, animatorOverrideAnimationLookup, acc.animations, animatorOverrideAnimationsMap);
ProcessLayer(ref acc.controller.Value, acc.layerIndex, acpc, aclc, ref events, triggersToReset);
if (events.IsCreated)
{
EmitStateUpdateEvents(ref events, acc, startIndex);
startIndex = events.Length;
}
}
// Reset affected triggers
ResetTriggers(acpc, triggersToReset);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void ResetTriggers(NativeArray<AnimatorControllerParameterComponent> acpc, BitFieldN triggersToReset)
{
if (!triggersToReset.TestAny())
return;
for (var i = 0; i < acpc.Length; ++i)
{
var p = acpc[i];
if (p.type == ControllerParameterType.Trigger && triggersToReset.IsSet(i))
{
p.BoolValue = false;
acpc[i] = p;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
RuntimeAnimatorData.StateData InitControllerStateData(ref RuntimeAnimatorData rtd, int stateID)
{
var rv = rtd.MakeDefaultState();
rv.id = stateID;
rv.normalizedDuration = 0;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void TryExitTransition(ref AnimatorControllerLayerComponent acc, ref DynamicBuffer<AnimatorControllerEventComponent> events)
{
if (acc.rtd.activeTransition.id < 0)
return;
if (CheckTransitionExitConditions(acc.rtd.activeTransition))
{
// Add state exit event
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateExit, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
acc.rtd.srcState = acc.rtd.dstState;
acc.rtd.ClearStateSnapshots();
acc.rtd.dstState = acc.rtd.MakeDefaultState();
acc.rtd.activeTransition = acc.rtd.MakeDefaultTransition();
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
int GetValidTransitionForCurrentFrame
(
ref BlobArray<TransitionBlob> transitions,
float normalizedStateDuration,
float srcStateDurationFrameDelta,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
BitFieldN triggersToReset
)
{
var isTransitionFits = false;
int i = 0;
for (; i < transitions.Length && !isTransitionFits; ++i)
{
ref var t = ref transitions[i];
isTransitionFits =
CheckTransitionEnterExitTimeCondition(ref t, normalizedStateDuration, srcStateDurationFrameDelta) &&
CheckTransitionEnterConditions(ref t, runtimeParams, triggersToReset);
}
return isTransitionFits ? i - 1 : -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void TryEnterTransition
(
in DynamicBuffer<AnimatorControllerLayerComponent> aclc,
ref ControllerBlob controllerBlob,
int layerIndex,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float srcStateDurationFrameDelta,
float curStateDuration,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
BitFieldN triggersToReset
)
{
ref var acc = ref aclc.ElementAt(layerIndex);
ref var layer = ref controllerBlob.layers[layerIndex];
ref var currentState = ref layer.states[acc.rtd.srcState.id];
if (acc.rtd.activeTransition.id >= 0)
{
TryTransitionInterruption(ref layer, ref acc.rtd, srcStateDurationFrameDelta, runtimeParams, triggersToReset);
return;
}
var newTransitionIndex = GetValidTransitionForCurrentFrame(ref currentState.transitions, acc.rtd.srcState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersToReset);
if (newTransitionIndex < 0)
return;
ref var t = ref currentState.transitions[newTransitionIndex];
var timeShouldBeInTransition = GetTimeInSecondsShouldBeInTransition(ref t, acc.rtd.srcState.normalizedDuration, curStateDuration, srcStateDurationFrameDelta);
acc.rtd.activeTransition.id = newTransitionIndex;
acc.rtd.activeTransition.length = GetTransitionLength(ref t);
acc.rtd.activeTransition.normalizedDuration = timeShouldBeInTransition / CalculateTransitionDuration(acc.rtd.activeTransition, curStateDuration);
var dstStateDur = CalculateStateDuration(layerIndex, t.targetStateId, ref controllerBlob, aclc.AsNativeArray(), runtimeParams);
acc.rtd.dstState = InitControllerStateData(ref acc.rtd, t.targetStateId);
acc.rtd.dstState.normalizedDuration += timeShouldBeInTransition / dstStateDur + t.offset;
// Add state enter event
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateEnter, acc, acc.rtd.dstState.id, acc.rtd.dstState.normalizedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void EmitEvent
(
ref DynamicBuffer<AnimatorControllerEventComponent> events,
AnimatorControllerEventComponent.EventType eventType,
AnimatorControllerLayerComponent aclc,
int stateId,
float stateDuration
)
{
if (!events.IsCreated)
return;
var evt = new AnimatorControllerEventComponent()
{
eventType = eventType,
stateId = stateId,
layerId = aclc.layerIndex,
timeInState = stateDuration,
};
#if RUKHANKA_DEBUG_INFO
aclc.controller.Value.layers[aclc.layerIndex].states[stateId].name.CopyToWithTruncate(ref evt.stateName);
#endif
events.Add(evt);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void EmitStateUpdateEvents(ref DynamicBuffer<AnimatorControllerEventComponent> events, in AnimatorControllerLayerComponent acc, int startIndex)
{
if (!events.IsCreated)
return;
var srcStateEnterExit = false;
var dstStateEnterExit = false;
for (var i = startIndex; i < events.Length; ++i)
{
var e = events[i];
if (acc.rtd.srcState.id >= 0 && e.stateId == acc.rtd.srcState.id)
srcStateEnterExit = true;
if (acc.rtd.dstState.id >= 0 && e.stateId == acc.rtd.dstState.id)
dstStateEnterExit = true;
}
if (acc.rtd.srcState.id >= 0 && !srcStateEnterExit)
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateUpdate, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
if (acc.rtd.dstState.id >= 0 && !dstStateEnterExit)
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateUpdate, acc, acc.rtd.dstState.id, acc.rtd.dstState.normalizedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessLayer
(
ref ControllerBlob c,
int layerIndex,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
DynamicBuffer<AnimatorControllerLayerComponent> aclc,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
BitFieldN triggersToReset
)
{
ref var layer = ref c.layers[layerIndex];
ref var acc = ref aclc.ElementAt(layerIndex);
var currentStateID = acc.rtd.srcState.id;
if (currentStateID < 0)
currentStateID = layer.defaultStateIndex;
// Adjust delta time according to the layer animator speed
var layerDeltaTime = dt * acc.speed;
var curStateDuration = CalculateStateDuration(layerIndex, currentStateID, ref c, aclc.AsNativeArray(), runtimeParams);
if (Hint.Unlikely(acc.rtd.srcState.id < 0))
{
acc.rtd.srcState = InitControllerStateData(ref acc.rtd, layer.defaultStateIndex);
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateEnter, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
}
var srcStateDurationFrameDelta = CalculateStateFrameDeltaSafe(layerDeltaTime, curStateDuration);
acc.rtd.srcState.normalizedDuration += srcStateDurationFrameDelta;
if (acc.rtd.dstState.id >= 0)
{
var dstStateDuration = CalculateStateDuration(layerIndex, acc.rtd.dstState.id, ref c, aclc.AsNativeArray(), runtimeParams);
acc.rtd.dstState.normalizedDuration += CalculateStateFrameDeltaSafe(layerDeltaTime, dstStateDuration);
}
if (acc.rtd.activeTransition.id >= 0)
{
var transitionDuration = CalculateTransitionDuration(acc.rtd.activeTransition, curStateDuration);
acc.rtd.activeTransition.normalizedDuration += layerDeltaTime / transitionDuration;
}
TryExitTransition(ref acc, ref events);
TryEnterTransition(aclc, ref c, layerIndex, runtimeParams, srcStateDurationFrameDelta, curStateDuration, ref events, triggersToReset);
// Check transition exit conditions one more time in case of Enter->Exit sequence appeared in single frame
TryExitTransition(ref acc, ref events);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateStateFrameDeltaSafe(float dt, float stateDuration)
{
var rv = dt / stateDuration;
rv = math.select(0, rv, math.isfinite(rv));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateMotionDuration
(
ref MotionBlob mb,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight
)
{
if (weight == 0) return 0;
switch (mb.type)
{
case MotionBlob.Type.None:
return 1;
case MotionBlob.Type.AnimationClip:
var animationHash = controllerAnimationsBlob.Value.animations[mb.animationIndex];
var animBlob = BlobDatabaseSingleton.GetBlobAsset(animationHash, animationDatabase);
if (animBlob != BlobAssetReference<AnimationClipBlob>.Null)
return animBlob.Value.length * weight;
return 1;
}
var childMotions = ScriptedAnimator.GetChildMotionsList(ref mb, runtimeParams);
var rv = CalculateBlendTreeMotionDuration(childMotions, ref mb.blendTree.motions, runtimeParams, weight);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateBlendTreeMotionDuration
(
NativeList<ScriptedAnimator.MotionIndexAndWeight> miwArr,
ref BlobArray<ChildMotionBlob> motions,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight
)
{
if (!miwArr.IsCreated || miwArr.IsEmpty)
return 1;
var weightSum = 0.0f;
for (int i = 0; i < miwArr.Length; ++i)
weightSum += miwArr[i].weight;
// If total weight less then 1, normalize weights
if (Hint.Unlikely(weightSum < 1))
{
for (int i = 0; i < miwArr.Length; ++i)
{
var miw = miwArr[i];
miw.weight = miw.weight / weightSum;
miwArr[i] = miw;
}
}
var rv = 0.0f;
for (int i = 0; i < miwArr.Length; ++i)
{
var miw = miwArr[i];
ref var m = ref motions[miw.motionIndex];
rv += CalculateMotionDuration(ref m.motion, runtimeParams, weight * miw.weight) / m.timeScale;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Negative value means that length is a normalized value from source state length
float GetTransitionLength(ref TransitionBlob tb)
{
return math.select(-tb.duration, tb.duration, tb.hasFixedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateTransitionDuration(in RuntimeAnimatorData.TransitionData trd, float curStateDuration)
{
var rv = math.abs(trd.length);
if (trd.length < 0)
{
rv *= curStateDuration;
}
return math.max(rv, 0.0001f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateStateDuration
(
int layerIndex,
int stateId,
ref ControllerBlob controllerBlob,
in NativeArray<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams
)
{
ref var layer = ref controllerBlob.layers[layerIndex];
ref var sb = ref layer.states[stateId];
var motionDuration = CalculateMotionDuration(ref sb.motion, runtimeParams, 1);
// In case of layer sync option is enabled, adjust state duration with respect to "timing" property
if (Hint.Unlikely(layer.syncedLayerIndex >= 0))
{
// Override controller must be exact copy of current
ref var baseState = ref controllerBlob.layers[layer.syncedLayerIndex].states[stateId];
var baseMotionDuration = CalculateMotionDuration(ref baseState.motion, runtimeParams, 1);
var weightedMotionDuration = math.lerp(baseMotionDuration, motionDuration, aclc[layerIndex].weight);
motionDuration = math.select(baseMotionDuration, weightedMotionDuration, layer.syncedTiming > 0);
}
else if (Hint.Unlikely(layer.syncedTiming >= 0))
{
ref var syncedState = ref controllerBlob.layers[layer.syncedTiming].states[stateId];
var syncedStateDuration = CalculateMotionDuration(ref syncedState.motion, runtimeParams, 1);
motionDuration = math.lerp(motionDuration, syncedStateDuration, aclc[layer.syncedTiming].weight);
}
var speedMultiplier = 1.0f;
if (sb.speedMultiplierParameterIndex >= 0)
{
speedMultiplier = runtimeParams[sb.speedMultiplierParameterIndex].FloatValue;
}
var rv = motionDuration / (sb.speed * speedMultiplier);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static float GetLoopAwareTransitionExitTime(float exitTime, float normalizedDuration, float speedSign)
{
var rv = exitTime;
if (exitTime <= 1.0f)
{
// Unity animator logic and documentation mismatch. Documentation says that exit time loop condition should be when transition exitTime less then 1, but in practice it will loop when exitTime is less or equal(!) to 1.
exitTime = math.min(exitTime, 0.9999f);
var snd = normalizedDuration * speedSign;
var f = math.frac(snd);
rv += (int)snd;
if (f > exitTime)
rv += 1;
}
return rv * speedSign;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float GetTimeInSecondsShouldBeInTransition(ref TransitionBlob tb, float normalizedStateDuration, float curStateDuration, float frameDT)
{
if (!tb.hasExitTime) return 0;
// This should be always less then curStateRTD.normalizedDuration
var loopAwareExitTime = GetLoopAwareTransitionExitTime(tb.exitTime, normalizedStateDuration - frameDT, math.sign(frameDT));
var loopDelta = normalizedStateDuration - loopAwareExitTime;
var rv = loopDelta * curStateDuration;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionEnterExitTimeCondition(ref TransitionBlob tb, float normalizedStateDuration, float srcStateDurationFrameDelta)
{
var noNormalConditions = tb.conditions.Length == 0;
if (!tb.hasExitTime) return !noNormalConditions;
var l0 = normalizedStateDuration - srcStateDurationFrameDelta;
var l1 = normalizedStateDuration;
var speedSign = math.select(-1, 1, l0 < l1);
var loopAwareExitTime = GetLoopAwareTransitionExitTime(tb.exitTime, l0, speedSign);
if (speedSign < 0)
(l0, l1) = (l1, l0);
var rv = loopAwareExitTime > l0 && loopAwareExitTime <= l1;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckIntCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.Equals:
if (param.IntValue != c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.Greater:
if (param.IntValue <= c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.Less:
if (param.IntValue >= c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.NotEqual:
if (param.IntValue == c.threshold.intValue) rv = false;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckFloatCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.Greater:
if (param.FloatValue <= c.threshold.floatValue) rv = false;
break;
case AnimatorConditionMode.Less:
if (param.FloatValue >= c.threshold.floatValue) rv = false;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckBoolCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.If:
rv = param.BoolValue;
break;
case AnimatorConditionMode.IfNot:
rv = !param.BoolValue;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void MarkTriggersToReset(ref TransitionBlob tb, BitFieldN triggersToReset)
{
for (int i = 0; i < tb.conditions.Length; ++i)
{
ref var c = ref tb.conditions[i];
// Mark all transition parameters as "need to be reset". We will check actual parameter type later, after all layers processing
triggersToReset.Set(c.paramIdx, true);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionEnterConditions(ref TransitionBlob tb, NativeArray<AnimatorControllerParameterComponent> runtimeParams, BitFieldN triggersToReset)
{
if (tb.conditions.Length == 0)
return true;
var rv = true;
var hasTriggers = false;
for (int i = 0; i < tb.conditions.Length && rv; ++i)
{
ref var c = ref tb.conditions[i];
var param = runtimeParams[c.paramIdx];
switch (param.type)
{
case ControllerParameterType.Float:
rv = CheckFloatCondition(param, ref c);
break;
case ControllerParameterType.Int:
rv = CheckIntCondition(param, ref c);
break;
case ControllerParameterType.Bool:
rv = CheckBoolCondition(param, ref c);
break;
case ControllerParameterType.Trigger:
rv = CheckBoolCondition(param, ref c);
hasTriggers = true;
break;
}
}
if (hasTriggers && rv)
MarkTriggersToReset(ref tb, triggersToReset);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionExitConditions(RuntimeAnimatorData.TransitionData transitionRuntimeData)
{
return transitionRuntimeData.normalizedDuration >= 1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void TryTransitionInterruption
(
ref LayerBlob layerBlob,
ref RuntimeAnimatorData rtd,
float srcStateDurationFrameDelta,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
BitFieldN triggersToReset
)
{
ref var srcStateTransitions = ref layerBlob.states[rtd.srcState.id].transitions;
var transitionIndex = rtd.activeTransition.id;
// Transition index could be greater than number of actual transitions when 'ScriptedAnimator.CrossFade' is used
if (transitionIndex >= srcStateTransitions.Length)
return;
// We are in transition right now. Perform transition interruption logic
ref var activeTransition = ref srcStateTransitions[transitionIndex];
// Can't interrupt, return
if (Hint.Likely(activeTransition.interruptionSource == TransitionBlob.InterruptionSource.None))
return;
// Make copy of triggers to reset flags because I don't know if there are valid interruption transitions yet
var triggersToResetMem = stackalloc uint[triggersToReset.SizeInInts()];
var triggersClone = triggersToReset.Clone(triggersToResetMem);
var transitionIndexCandidateSrc = -1;
var transitionIndexCandidateDst = -1;
var canBeInterrupted = false;
if
(
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.Source ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.SourceThenDestination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.DestinationThenSource
)
{
transitionIndexCandidateSrc = GetValidTransitionForCurrentFrame(ref srcStateTransitions, rtd.srcState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersClone);
canBeInterrupted |=
transitionIndexCandidateSrc >= 0 &&
(activeTransition.orderedInterruption ? transitionIndexCandidateSrc < transitionIndex : transitionIndexCandidateSrc != transitionIndex);
}
ref var dstStateTransitions = ref layerBlob.states[rtd.dstState.id].transitions;
if
(
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.Destination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.SourceThenDestination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.DestinationThenSource
)
{
transitionIndexCandidateDst = GetValidTransitionForCurrentFrame(ref dstStateTransitions, rtd.dstState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersClone);
canBeInterrupted |= transitionIndexCandidateDst >= 0;
}
if (!canBeInterrupted)
return;
// There is valid interruption
// Select interrupting transition
var interruptingTransitionID = -1;
TransitionBlob* transitionBlobsPtr = null;
switch (activeTransition.interruptionSource)
{
case TransitionBlob.InterruptionSource.Source:
interruptingTransitionID = transitionIndexCandidateSrc;
transitionBlobsPtr = (TransitionBlob*)srcStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.Destination:
interruptingTransitionID = transitionIndexCandidateDst;
transitionBlobsPtr = (TransitionBlob*)dstStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.SourceThenDestination:
interruptingTransitionID = math.select(transitionIndexCandidateDst, transitionIndexCandidateSrc, transitionIndexCandidateSrc >= 0);
transitionBlobsPtr = transitionIndexCandidateSrc >= 0 ? (TransitionBlob*)srcStateTransitions.GetUnsafePtr() : (TransitionBlob*)dstStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.DestinationThenSource:
interruptingTransitionID = math.select(transitionIndexCandidateSrc, transitionIndexCandidateDst, transitionIndexCandidateDst >= 0);
transitionBlobsPtr = transitionIndexCandidateDst >= 0 ? (TransitionBlob*)dstStateTransitions.GetUnsafePtr() : (TransitionBlob*)srcStateTransitions.GetUnsafePtr();
break;
default:
// This should not happen
BurstAssert.IsTrue(false, "Transition interruption invalid code path");
return;
}
ref var newTransition = ref transitionBlobsPtr[interruptingTransitionID];
// Copy triggers back to original bitset
triggersToReset.CopyFrom(triggersClone);
// If state snapshot array is empty, then push both destination state and source state
// If there are some state snapshots in array, the these snapshots are our "source state" already, so we need to add only destination state
if (rtd.srcStateSnapshots.IsEmpty)
rtd.PushStateSnapshot(rtd.srcState.id, 1, rtd.srcState.normalizedDuration, rtd.srcState.motionId);
rtd.PushStateSnapshot(rtd.dstState.id, rtd.activeTransition.normalizedDuration, rtd.dstState.normalizedDuration, rtd.dstState.motionId);
rtd.srcState.normalizedDuration = 0;
// If interrupting transition belongs to next state, configure srcState accordingly
if (transitionBlobsPtr == dstStateTransitions.GetUnsafePtr())
rtd.srcState.id = rtd.dstState.id;
rtd.activeTransition.id = interruptingTransitionID;
rtd.activeTransition.length = GetTransitionLength(ref newTransition);
rtd.activeTransition.normalizedDuration = 0;
rtd.dstState = InitControllerStateData(ref rtd, newTransition.targetStateId);
rtd.dstState.normalizedDuration = newTransition.offset;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 66f7113131a9adf41bd19f34c3f9434c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorControllerSystem_Job.cs
uploadId: 897522
@@ -0,0 +1,182 @@
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorParametersAspect
{
public AnimatorControllerParameterIndexTableComponent indexTable;
public DynamicBuffer<AnimatorControllerParameterComponent> parametersArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public AnimatorParametersAspect(DynamicBuffer<AnimatorControllerParameterComponent> parametersArr, AnimatorControllerParameterIndexTableComponent indexTable)
{
this.parametersArr = parametersArr;
this.indexTable = indexTable;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public float GetFloatParameter(FastAnimatorParameter fp) => GetParameterValue(fp).floatValue;
public int GetIntParameter(FastAnimatorParameter fp) => GetParameterValue(fp).intValue;
public bool GetBoolParameter(FastAnimatorParameter fp) => GetParameterValue(fp).boolValue;
public float GetFloatParameter(uint h) => GetParameterValue(h).floatValue;
public int GetIntParameter(uint h) => GetParameterValue(h).intValue;
public bool GetBoolParameter(uint h) => GetParameterValue(h).boolValue;
public float GetFloatParameter(FixedStringName n) => GetParameterValue(n).floatValue;
public int GetIntParameter(FixedStringName n) => GetParameterValue(n).intValue;
public bool GetBoolParameter(FixedStringName n) => GetParameterValue(n).boolValue;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(FastAnimatorParameter fp)
{
ParameterValue rv;
if (indexTable.value.IsCreated)
fp.GetRuntimeParameterData(indexTable.value, parametersArr, out rv);
else
fp.GetRuntimeParameterData(parametersArr, out rv);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(uint parameterHash)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
return GetParameterValue(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(FixedStringName parameterName)
{
var fp = new FastAnimatorParameter(parameterName);
return GetParameterValue(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValueByIndex(int paramIndex)
{
return parametersArr.ElementAt(paramIndex).value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(FastAnimatorParameter fp, ParameterValue value)
{
if (indexTable.value.IsCreated)
fp.SetRuntimeParameterData(indexTable.value, parametersArr, value);
else
fp.SetRuntimeParameterData(parametersArr, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetTrigger(FastAnimatorParameter fp)
{
SetParameterValue(fp, new ParameterValue() { boolValue = true });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void ResetTrigger(FastAnimatorParameter fp)
{
SetParameterValue(fp, new ParameterValue() { boolValue = false });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetIntParameter(FastAnimatorParameter fp, int v)
{
SetParameterValue(fp, new ParameterValue() { intValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetFloatParameter(FastAnimatorParameter fp, float v)
{
SetParameterValue(fp, new ParameterValue() { floatValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetBoolParameter(FastAnimatorParameter fp, bool v)
{
SetParameterValue(fp, new ParameterValue() { boolValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValueByIndex(int paramIndex, ParameterValue value)
{
parametersArr.ElementAt(paramIndex).value = value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(uint parameterHash, ParameterValue value)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
SetParameterValue(fp, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(FixedStringName parameterName, ParameterValue value)
{
var fp = new FastAnimatorParameter(parameterName);
SetParameterValue(fp, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public int GetParameterIndex(FastAnimatorParameter fp)
{
var index = indexTable.value.IsCreated ?
fp.GetRuntimeParameterIndex(indexTable.value, parametersArr) :
fp.GetRuntimeParameterIndex(parametersArr);
return index;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(FastAnimatorParameter fp)
{
return GetParameterIndex(fp) != -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(uint parameterHash)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
return HasParameter(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(FixedStringName parameterName)
{
var fp = new FastAnimatorParameter(parameterName);
return HasParameter(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public int ParametersCount() => parametersArr.Length;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 20aa9d2bf9a03ba45be985d8bc55cdd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorParameterAspect.cs
uploadId: 897522
@@ -0,0 +1,105 @@
using Rukhanka.Toolbox;
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public readonly struct AnimatorStateQueryAspect
{
readonly DynamicBuffer<AnimatorControllerLayerComponent> layersArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public AnimatorStateQueryAspect(DynamicBuffer<AnimatorControllerLayerComponent> layers)
{
layersArr = layers;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct RuntimeStateInfo
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName name;
#endif
public uint hash;
public float normalizedTime;
}
public struct RuntimeTransitionInfo
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName name;
#endif
public uint hash;
public float normalizedTime;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public RuntimeStateInfo GetLayerCurrentStateInfo(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
ref var layerBlob = ref layerRuntimeData.controller.Value.layers[layerIndex];
var curStateID = layerRuntimeData.rtd.srcState.id;
if (curStateID < 0 || curStateID >= layerBlob.states.Length)
return default;
var rv = new RuntimeStateInfo()
{
#if RUKHANKA_DEBUG_INFO
name = layerBlob.states[curStateID].name.ToFixedString(),
#endif
hash = layerBlob.states[curStateID].hash,
normalizedTime = layerRuntimeData.rtd.srcState.normalizedDuration,
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public RuntimeTransitionInfo GetLayerCurrentTransitionInfo(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
ref var layerBlob = ref layerRuntimeData.controller.Value.layers[layerIndex];
var curTransitionID = layerRuntimeData.rtd.activeTransition.id;
var curStateID = layerRuntimeData.rtd.srcState.id;
if (curTransitionID < 0 || curStateID < 0 || curStateID >= layerBlob.states.Length)
return default;
var rv = new RuntimeTransitionInfo()
{
#if RUKHANKA_DEBUG_INFO
name = layerBlob.states[curStateID].transitions[curTransitionID].name.ToFixedString(),
#endif
hash = layerBlob.states[curStateID].transitions[curTransitionID].hash,
normalizedTime = layerRuntimeData.rtd.activeTransition.normalizedDuration
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool IsInTransition(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
var rv = layerRuntimeData.rtd.activeTransition.id >= 0;
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e4d517a04d48e334796e296fb7c2d66e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/AnimatorStateQueryAspect.cs
uploadId: 897522
@@ -0,0 +1,159 @@
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
using UnityEngine;
using System;
using Rukhanka.Toolbox;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct FastAnimatorParameter
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName paramName;
#endif
public uint hash;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public FastAnimatorParameter(FixedStringName name)
{
hash = name.CalculateHash32();
#if RUKHANKA_DEBUG_INFO
paramName = name;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public FastAnimatorParameter(uint hash)
{
this.hash = hash;
#if RUKHANKA_DEBUG_INFO
paramName = default;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool GetRuntimeParameterDataInternal(int paramIdx, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
bool isValid = paramIdx >= 0;
if (isValid)
{
outData = runtimeParameters[paramIdx].value;
}
else
{
outData = default;
#if RUKHANKA_DEBUG_INFO
Debug.LogError($"Could find animator parameter with name {paramName} in hash table! Returning default value!");
#endif
}
return isValid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool GetRuntimeParameterData(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
var paramIdx = GetRuntimeParameterIndex(pt, runtimeParameters);
return GetRuntimeParameterDataInternal(paramIdx, runtimeParameters, out outData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool GetRuntimeParameterData(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
var paramIdx = GetRuntimeParameterIndex(runtimeParameters);
return GetRuntimeParameterDataInternal(paramIdx, runtimeParameters, out outData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool SetRuntimeParameterDataInternal(int paramIdx, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
bool isValid = paramIdx >= 0;
if (isValid)
{
var p = runtimeParameters[paramIdx];
p.value = paramData;
runtimeParameters[paramIdx] = p;
}
#if RUKHANKA_DEBUG_INFO
else
{
Debug.LogError($"Could find animator parameter with name {paramName} in hash table! Setting value is failed!");
}
#endif
return isValid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetRuntimeParameterData(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
var paramIdx = GetRuntimeParameterIndex(pt, runtimeParameters);
return SetRuntimeParameterDataInternal(paramIdx, runtimeParameters, paramData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetRuntimeParameterData(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
var paramIdx = GetRuntimeParameterIndex(runtimeParameters);
return SetRuntimeParameterDataInternal(paramIdx, runtimeParameters, paramData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetTrigger(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters) => SetRuntimeParameterData(pt, runtimeParameters, new ParameterValue() { boolValue = true });
public bool SetTrigger(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters) => SetRuntimeParameterData(runtimeParameters, new ParameterValue() { boolValue = true });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Linear search variant
public static int GetRuntimeParameterIndex(uint hash, in ReadOnlySpan<AnimatorControllerParameterComponent> parameters)
{
for (int i = 0; i < parameters.Length; ++i)
{
var p = parameters[i];
if (p.hash == hash)
return i;
}
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Perfect hash table variant
public static int GetRuntimeParameterIndex(uint hash, in BlobAssetReference<PerfectHashTableBlob> pt, in ReadOnlySpan<AnimatorControllerParameterComponent> parameters)
{
var paramIdx = pt.Value.Query(hash);
if (paramIdx < 0)
return -1;
return paramIdx;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe int GetRuntimeParameterIndex(in BlobAssetReference<PerfectHashTableBlob> pt, in DynamicBuffer<AnimatorControllerParameterComponent> acpc)
{
var span = new ReadOnlySpan<AnimatorControllerParameterComponent>(acpc.GetUnsafeReadOnlyPtr(), acpc.Length);
return GetRuntimeParameterIndex(hash, pt, span);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe int GetRuntimeParameterIndex(in DynamicBuffer<AnimatorControllerParameterComponent> acpc)
{
var span = new ReadOnlySpan<AnimatorControllerParameterComponent>(acpc.GetUnsafeReadOnlyPtr(), acpc.Length);
return GetRuntimeParameterIndex(hash, span);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fa18070f79c037d47b59593d7d6cc720
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/FastAnimatorParameter.cs
uploadId: 897522
@@ -0,0 +1,133 @@
using Rukhanka.WaybackMachine;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[BurstCompile]
[UpdateAfter(typeof(AnimatorControllerSystem<AnimatorControllerQuery>))]
public partial struct FillAnimationsFromControllerSystem: ISystem
{
EntityQuery fillAnimationsBufferQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
fillAnimationsBufferQuery = SystemAPI.QueryBuilder()
.WithAll<AnimatorControllerLayerComponent, AnimationToProcessComponent>()
.Build();
ss.RequireForUpdate(fillAnimationsBufferQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
if (SystemAPI.TryGetSingletonRW<InternalAnimatorDataSingleton>(out var internalDataSingleton))
internalDataSingleton.ValueRW.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var entityTypeHandle = SystemAPI.GetEntityTypeHandle();
var controllerLayersBufferHandleRO = SystemAPI.GetBufferTypeHandle<AnimatorControllerLayerComponent>(true);
var controllerParametersBufferHandleRO = SystemAPI.GetBufferTypeHandle<AnimatorControllerParameterComponent>(true);
var animatorOverrideAnimationsLookup = SystemAPI.GetComponentLookup<AnimatorOverrideAnimations>(true);
var animationToProcessBufferHandle = SystemAPI.GetBufferTypeHandle<AnimationToProcessComponent>();
var animDBSingleton = SystemAPI.GetSingleton<BlobDatabaseSingleton>();
var internalDataSingletonQuery = SystemAPI.QueryBuilder().WithAllRW<InternalAnimatorDataSingleton>().Build();
var internalAnimatorData = GetInternalDataSingleton(internalDataSingletonQuery, ref ss);
var fillAnimationsBufferJob = new FillAnimationsBufferJob()
{
controllerLayersBufferHandle = controllerLayersBufferHandleRO,
controllerParametersBufferHandle = controllerParametersBufferHandleRO,
animationToProcessBufferHandle = animationToProcessBufferHandle,
animatorOverrideAnimationLookup = animatorOverrideAnimationsLookup,
entityTypeHandle = entityTypeHandle,
animationDatabase = animDBSingleton.animations,
avatarMaskDatabase = animDBSingleton.avatarMasks,
animatorOverrideAnimationsMap = internalAnimatorData.animatorOverrideAnimationsMap.AsParallelWriter()
};
ss.Dependency = fillAnimationsBufferJob.ScheduleParallel(fillAnimationsBufferQuery, ss.Dependency);
ss.Dependency = CopyEventsForWaybackMachineDuringRecording(ref ss, ss.Dependency);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static InternalAnimatorDataSingleton GetInternalDataSingleton(EntityQuery singletonQuery, ref SystemState ss)
{
if (singletonQuery.TryGetSingletonRW<InternalAnimatorDataSingleton>(out var internalAnimatorData))
return internalAnimatorData.ValueRW;
var iads = InternalAnimatorDataSingleton.MakeDefault();
ss.EntityManager.CreateSingleton(iads, "Rukhanka.InternalAnimatorDataSingleton");
return iads;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static unsafe BlobAssetReference<ControllerAnimationsBlob> GetControllerAnimationsBlob
(
Entity e,
ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup,
BlobAssetReference<ControllerAnimationsBlob> cab,
NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap
)
{
if (animatorOverrideAnimationLookup.TryGetComponent(e, out var animationOverrides) && animatorOverrideAnimationLookup.IsComponentEnabled(e))
{
// Try cache first
var combinedHash = animationOverrides.value.GetHashCode() ^ cab.GetHashCode();
if (UnsafeParallelHashMapBase<int, BlobAssetReference<ControllerAnimationsBlob>>
.TryGetFirstValueAtomic(animatorOverrideAnimationsMap.m_Writer.m_Buffer, combinedHash, out var rv, out _))
return rv;
// Merge controller animations and override animations
var bb = new BlobBuilder(Allocator.Temp);
ref var mergedBlobAsset = ref bb.ConstructRoot<ControllerAnimationsBlob>();
var animsArr = bb.Allocate(ref mergedBlobAsset.animations, cab.Value.animations.Length);
for (var i = 0; i < animsArr.Length; ++i)
{
var overrideAnim = animationOverrides.value.Value.animations[i];
animsArr[i] = overrideAnim.IsValid ? overrideAnim : cab.Value.animations[i];
}
rv = bb.CreateBlobAssetReference<ControllerAnimationsBlob>(Allocator.Persistent);
animatorOverrideAnimationsMap.TryAdd(combinedHash, rv);
return rv;
}
return cab;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle CopyEventsForWaybackMachineDuringRecording(ref SystemState ss, JobHandle dependsOn)
{
if (!SystemAPI.TryGetSingletonRW<RecordComponent>(out var rcd))
return dependsOn;
var copyEventsToWaybackMachineJob = new CopyAnimatorEventsToWaybackMachineRecordingJob()
{
outEvents = rcd.ValueRW.wbData.Value.emittedAnimatorEvents
};
var jh = copyEventsToWaybackMachineJob.Schedule(dependsOn);
return jh;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8142c7ce247b2d4449bacfa4ece227cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/FillAnimationsFromControllerSystem.cs
uploadId: 897522
@@ -0,0 +1,271 @@
using System;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using static Rukhanka.AnimatorControllerSystemJobs;
using Rukhanka.WaybackMachine;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial struct FillAnimationsFromControllerSystem
{
[BurstCompile]
struct FillAnimationsBufferJob: IJobChunk
{
[ReadOnly]
public BufferTypeHandle<AnimatorControllerLayerComponent> controllerLayersBufferHandle;
[ReadOnly]
public BufferTypeHandle<AnimatorControllerParameterComponent> controllerParametersBufferHandle;
[ReadOnly]
public ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup;
[ReadOnly]
public EntityTypeHandle entityTypeHandle;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> animationDatabase;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> avatarMaskDatabase;
public BufferTypeHandle<AnimationToProcessComponent> animationToProcessBufferHandle;
public NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap;
BlobAssetReference<ControllerAnimationsBlob> controllerAnimationsBlob;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var layerBuffers = chunk.GetBufferAccessor(ref controllerLayersBufferHandle);
var parameterBuffers = chunk.GetBufferAccessor(ref controllerParametersBufferHandle);
var animationsToProcessBuffers = chunk.GetBufferAccessor(ref animationToProcessBufferHandle);
var entities = chunk.GetNativeArray(entityTypeHandle);
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
while (cee.NextEntityIndex(out var i))
{
var layers = layerBuffers[i].AsNativeArray();
var parameters = parameterBuffers.Length > 0 ? parameterBuffers[i].AsNativeArray() : default;
var e = entities[i];
var animsBuf = animationsToProcessBuffers[i];
AddAnimationsForEntity(ref animsBuf, layers, parameters, e);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AnimationsPostSetup(Span<AnimationToProcessComponent> animations, ref LayerBlob lb, int layerIndex, float weightMultiplier, float layerWeight)
{
// Set blending mode and adjust animations weight according to layer weight
for (int k = 0; k < animations.Length; ++k)
{
var a = animations[k];
a.blendMode = lb.blendingMode;
a.layerWeight = layerWeight;
a.layerIndex = layerIndex;
a.weight *= weightMultiplier;
a.avatarMask = BlobDatabaseSingleton.GetBlobAsset(lb.avatarMaskBlobHash, avatarMaskDatabase);
animations[k] = a;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void AddAnimationsForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> animations,
in NativeArray<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
Entity entity
)
{
if (entity == Entity.Null)
return;
animations.Clear();
// Need to skip zero weight layers
for (int i = 0; i < aclc.Length; ++i)
{
var animationCurIndex = animations.Length;
var l = aclc[i];
controllerAnimationsBlob = GetControllerAnimationsBlob(entity, animatorOverrideAnimationLookup, l.animations, animatorOverrideAnimationsMap);
var cb = l.controller;
ref var lb = ref cb.Value.layers[i];
if (l.weight == 0 || l.rtd.srcState.id < 0)
continue;
ref var srcState0Blob = ref lb.states[l.rtd.srcState.id];
var srcStateWeight = 1.0f;
var dstStateWeight = 0.0f;
if (l.rtd.activeTransition.id >= 0)
{
dstStateWeight = l.rtd.activeTransition.normalizedDuration;
srcStateWeight = (1 - dstStateWeight);
}
var srcStateTime = GetDurationTime(ref srcState0Blob, runtimeParams, l.rtd.srcState.normalizedDuration);
var dstStateAnimCount = 0;
if (l.rtd.dstState.id >= 0)
{
ref var dstStateBlob = ref lb.states[l.rtd.dstState.id];
var dstStateTime = GetDurationTime(ref dstStateBlob, runtimeParams, l.rtd.dstState.normalizedDuration);
dstStateAnimCount = AddMotionForEntity(ref animations, ref dstStateBlob.motion, runtimeParams, 1, dstStateTime, l.rtd.dstState.motionId);
}
var srcStateAnimCount = 0;
// No state snapshots - no transition interruption process
// Default state motion processing
if (Hint.Likely(l.rtd.srcStateSnapshots.Length == 0))
{
ref var srcStateBlob = ref lb.states[l.rtd.srcState.id];
srcStateAnimCount += AddMotionForEntity(ref animations, ref srcStateBlob.motion, runtimeParams, 1, srcStateTime, l.rtd.srcState.motionId);
}
// Transition interruption motions from state snapshots
for (var k = l.rtd.srcStateSnapshots.length - 1; k >= 0; --k)
{
var stateData = l.rtd.srcStateSnapshots[k];
ref var srcStateBlob = ref lb.states[stateData.id];
srcStateAnimCount += AddMotionForEntity(ref animations, ref srcStateBlob.motion, runtimeParams, stateData.weight, stateData.normalizedTime, stateData.motionId);
}
var animStartPtr = (AnimationToProcessComponent*)animations.GetUnsafePtr() + animationCurIndex;
var dstAnimsSpan = new Span<AnimationToProcessComponent>(animStartPtr, dstStateAnimCount);
var srcAnimsSpan = new Span<AnimationToProcessComponent>(animStartPtr + dstStateAnimCount, srcStateAnimCount);
var dstLayerMultiplier = math.select(dstStateWeight, 1, srcStateAnimCount > 0);
var srcLayerMultiplier = math.select(srcStateWeight, 1, dstStateAnimCount > 0);
dstStateWeight = math.select(1, dstStateWeight, srcStateAnimCount > 0);
srcStateWeight = math.select(1, srcStateWeight, dstStateAnimCount > 0);
AnimationsPostSetup(dstAnimsSpan, ref lb, i, dstStateWeight, dstLayerMultiplier * l.weight);
AnimationsPostSetup(srcAnimsSpan, ref lb, i, srcStateWeight, srcLayerMultiplier * l.weight);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AddAnimationForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
ref MotionBlob mb,
float weight,
float normalizedStateTime,
uint motionId
)
{
var atp = new AnimationToProcessComponent();
var animationHash = controllerAnimationsBlob.Value.animations[mb.animationIndex];
atp.animation = BlobDatabaseSingleton.GetBlobAsset(animationHash, animationDatabase);
atp.weight = weight;
atp.time = normalizedStateTime;
atp.motionId = mb.hash + motionId;
outAnims.Add(atp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AddMotionsFromBlendtree
(
in NativeList<ScriptedAnimator.MotionIndexAndWeight> miws,
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
ref BlobArray<ChildMotionBlob> motions,
float weight,
float normalizedStateTime,
uint motionId
)
{
for (int i = 0; i < miws.Length; ++i)
{
var miw = miws[i];
ref var m = ref motions[miw.motionIndex];
var finalWeight = weight * miw.weight;
if (finalWeight > 0)
AddMotionForEntity(ref outAnims, ref m.motion, runtimeParams, finalWeight, normalizedStateTime, (uint)(i + motionId));
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
int AddMotionForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
ref MotionBlob mb,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight,
float normalizedStateTime,
uint motionId
)
{
var startLen = outAnims.Length;
switch (mb.type)
{
case MotionBlob.Type.None:
break;
case MotionBlob.Type.AnimationClip:
AddAnimationForEntity(ref outAnims, ref mb, weight, normalizedStateTime, motionId);
break;
}
var childMotions = ScriptedAnimator.GetChildMotionsList(ref mb, runtimeParams);
if (childMotions.IsCreated)
{
AddMotionsFromBlendtree(childMotions, ref outAnims, runtimeParams, ref mb.blendTree.motions, weight, normalizedStateTime, motionId);
}
return outAnims.Length - startLen;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float GetDurationTime(ref StateBlob sb, in NativeArray<AnimatorControllerParameterComponent> runtimeParams, float normalizedDuration)
{
var timeDuration = normalizedDuration;
if (sb.timeParameterIndex >= 0)
{
timeDuration = runtimeParams[sb.timeParameterIndex].FloatValue;
}
var stateCycleOffset = sb.cycleOffset;
if (sb.cycleOffsetParameterIndex >= 0)
{
stateCycleOffset = runtimeParams[sb.cycleOffsetParameterIndex].FloatValue;
}
timeDuration += stateCycleOffset;
return timeDuration;
}
}
//=================================================================================================================//
[BurstCompile]
[WithAll(typeof(RecordComponent))]
partial struct CopyAnimatorEventsToWaybackMachineRecordingJob: IJobEntity
{
public NativeList<AnimatorControllerEventComponent> outEvents;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in DynamicBuffer<AnimatorControllerEventComponent> acec)
{
outEvents.AddRange(acec.AsNativeArray());
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f9daf9e1f7afc80459aad91d4b092a7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/FillAnimationsFromControllerSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,33 @@
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct InternalAnimatorDataSingleton: IComponentData
{
internal NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>> animatorOverrideAnimationsMap;
/////////////////////////////////////////////////////////////////////////////////
public static InternalAnimatorDataSingleton MakeDefault()
{
var rv = new InternalAnimatorDataSingleton()
{
animatorOverrideAnimationsMap = new (0xff, Allocator.Persistent)
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
foreach (var kv in animatorOverrideAnimationsMap)
kv.Value.Dispose();
animatorOverrideAnimationsMap.Dispose();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c7c1e038272b2944aab7454e9960315e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/InternalAnimatorDataSingleton.cs
uploadId: 897522
@@ -0,0 +1,114 @@
using Unity.Collections;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct RuntimeAnimatorData
{
public struct StateSnapshot
{
public int id;
public uint motionId;
public float weight;
public float normalizedTime;
}
//-------------------------------------------------------------------------------------------------//
public struct StateData
{
public int id;
public uint motionId;
public float normalizedDuration;
}
//-------------------------------------------------------------------------------------------------//
public struct TransitionData
{
public int id;
public float normalizedDuration;
public float length;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public StateData MakeDefaultState() => new StateData() { id = -1, normalizedDuration = 0, motionId = GetNextMotionID() };
public TransitionData MakeDefaultTransition() => new TransitionData() { id = -1, length = 0, normalizedDuration = 0 };
/////////////////////////////////////////////////////////////////////////////////////////////////////
uint GetNextMotionID()
{
motionIdCounter += 0xff;
return motionIdCounter;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public uint motionIdCounter;
public StateData srcState;
public StateData dstState;
public TransitionData activeTransition;
#if RUKHANKA_WITH_NETCODE
[GhostField(SendData = false)]
#endif
public FixedList64Bytes<StateSnapshot> srcStateSnapshots;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void ClearStateSnapshots() { srcStateSnapshots.Clear(); }
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void PushStateSnapshot(int stateID, float weight, float normalizedTime, uint motionId)
{
// If we are out of free space prune snapshot with the lowest weight
if (srcStateSnapshots.length == srcStateSnapshots.Capacity)
{
var minWeight = 1.0f;
var minWeightIndex = 0;
for (var i = 0; i < srcStateSnapshots.length; ++i)
{
var w = srcStateSnapshots[i].weight;
if (minWeight > w)
{
minWeight = w;
minWeightIndex = i;
}
}
if (minWeightIndex != srcStateSnapshots.length - 1)
srcStateSnapshots[minWeightIndex] = srcStateSnapshots[^1];
srcStateSnapshots.Length -= 1;
}
// Scale existing weights
for (var i = 0; i < srcStateSnapshots.length; ++i)
{
ref var sn = ref srcStateSnapshots.ElementAt(i);
sn.weight *= 1 - weight;
}
var ss = new StateSnapshot() { id = stateID, weight = weight, normalizedTime = normalizedTime, motionId = motionId };
srcStateSnapshots.Add(ss);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public static RuntimeAnimatorData MakeDefault()
{
var rv = new RuntimeAnimatorData();
rv.srcState = rv.MakeDefaultState();
rv.dstState = rv.MakeDefaultState();
rv.activeTransition = rv.MakeDefaultTransition();
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: da31f172d48d3614dac5465f62e8efe0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimatorController/RuntimeAnimatorData.cs
uploadId: 897522