Today’s article continues the series by finding all the job-safe APIs in the Unity engine as of 2019.3. We’ll compare against 2019.1 to see what’s new!

For 2018.3, we started using a reflection-based script to find all of the job-safe APIs in Unity’s API. These are the ones that have [NativeMethod] or IsThreadSafe = true. In Unity 2019.1.3f1, we found 350 of such functions. In Unity 2019.3.0f6, we find 400!

Here is a diff of the job-safe functions in 2019.1 versus 2019.3:

7a8,11
> Analytics:    UnityEngine.Analytics.AnalyticsResult SetEventWithLimitEndPoint(System.String, System.String, Int32, System.String)
> Analytics:    UnityEngine.Analytics.AnalyticsResult SetEventWithLimitPriority(System.String, UnityEngine.Analytics.AnalyticsEventPriority, Int32, System.String)
> AndroidJNI:    IntPtr NewStringFromStr(System.String)
> AnimationCurve:    Boolean Internal_Equals(IntPtr)
19c23
< AnimationHumanStream:    Single InternalGetMuscle(UnityEngine.Experimental.Animations.MuscleHandle)
---
> AnimationHumanStream:    Single InternalGetMuscle(UnityEngine.Animations.MuscleHandle)
51c55
< AnimationHumanStream:    Void InternalSetMuscle(UnityEngine.Experimental.Animations.MuscleHandle, Single)
---
> AnimationHumanStream:    Void InternalSetMuscle(UnityEngine.Animations.MuscleHandle, Single)
52a57,58
> AnimationSceneHandleUtility:    Void ReadSceneFloatsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32)
> AnimationSceneHandleUtility:    Void ReadSceneIntsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32)
57,58c63,64
< AnimationStream:    UnityEngine.Experimental.Animations.AnimationHumanStream GetHumanStream()
< AnimationStream:    UnityEngine.Experimental.Animations.AnimationStream InternalGetInputStream(Int32)
---
> AnimationStream:    UnityEngine.Animations.AnimationHumanStream GetHumanStream()
> AnimationStream:    UnityEngine.Animations.AnimationStream InternalGetInputStream(Int32)
66a73,76
> AnimationStreamHandleUtility:    Void ReadStreamFloatsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32)
> AnimationStreamHandleUtility:    Void ReadStreamIntsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32)
> AnimationStreamHandleUtility:    Void WriteStreamFloatsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32, Boolean)
> AnimationStreamHandleUtility:    Void WriteStreamIntsInternal(UnityEngine.Animations.AnimationStream ByRef, Void*, Void*, Int32, Boolean)
105a116,117
> BuildReferenceMap:    Byte[] SerializeToBinary()
> BuildReferenceMap:    Byte[] SerializeToBinary()
107a120,121
> BuildReferenceMap:    Void DeserializeFromBinary(Byte[])
> BuildReferenceMap:    Void DeserializeFromBinary(Byte[])
113a128,129
> BuildUsageTagSet:    Byte[] SerializeToBinary()
> BuildUsageTagSet:    Byte[] SerializeToBinary()
115a132,133
> BuildUsageTagSet:    Void DeserializeFromBinary(Byte[])
> BuildUsageTagSet:    Void DeserializeFromBinary(Byte[])
152,156d169
< DSPSampleProvider:    Int32 Internal_ReadFloatFromSampleProvider(Unity.Experimental.Audio.DSPSampleProvider, Void*, Int32)
< DSPSampleProvider:    Int32 Internal_ReadSInt16FromSampleProvider(Unity.Experimental.Audio.DSPSampleProvider, NativeFormatType, Void*, Int32)
< DSPSampleProvider:    Int32 Internal_ReadUInt8FromSampleProvider(Unity.Experimental.Audio.DSPSampleProvider, NativeFormatType, Void*, Int32)
< DSPSampleProvider:    UInt16 Internal_GetChannelCount(Unity.Experimental.Audio.DSPSampleProvider)
< DSPSampleProvider:    UInt32 Internal_GetSampleRate(Unity.Experimental.Audio.DSPSampleProvider)
161d173
< ExecuteContextInternal:    Void Internal_PostEvent(Void*, Int64, Void*, Int32)
179a192,198
> GraphicsFormatUtility:    Boolean IsCompressedTextureFormat(UnityEngine.TextureFormat)
> GraphicsFormatUtility:    UnityEngine.Experimental.Rendering.GraphicsFormat GetGraphicsFormat_Native_TextureFormat(UnityEngine.TextureFormat, Boolean)
> GraphicsFormatUtility:    UnityEngine.TextureFormat GetTextureFormat_Native_GraphicsFormat(UnityEngine.Experimental.Rendering.GraphicsFormat)
> GUID:    System.String GUIDToHexInternal(UnityEditor.GUID ByRef)
> GUID:    System.String GUIDToHexInternal(UnityEditor.GUID ByRef)
> GUID:    UnityEditor.GUID HexToGUIDInternal(System.String)
> GUID:    UnityEditor.GUID HexToGUIDInternal(System.String)
208a228,231
> MethodCollection:    System.Reflection.MethodInfo GetValue(IntPtr, Int32)
> MethodCollection:    System.Reflection.MethodInfo GetValue(IntPtr, Int32)
> MethodCollection:    Void Internal_CopyTo(System.Reflection.MethodInfo[], Int32)
> MethodCollection:    Void Internal_CopyTo(System.Reflection.MethodInfo[], Int32)
217a241
> NavMeshPath:    Void DestroyNavMeshPath(IntPtr)
229a254
> NavMeshQuery:    UnityEngine.Experimental.AI.PathQueryStatus GetEdgesAndNeighbors(IntPtr, UnityEngine.Experimental.AI.PolygonId, Int32, Int32, Void*, Void*, Void*, Int32 ByRef, Int32 ByRef)
242a268
> ParticleSystem:    Void CopyManagedJobData(Void*, UnityEngine.ParticleSystemJobs.NativeParticleData ByRef)
253c279,280
< ProfilerMarker:    IntPtr Internal_Create(System.String, Unity.Profiling.MarkerFlags)
---
> ProfilerMarker:    IntPtr Internal_Create(System.String, UInt16)
> ProfilerMarker:    System.String Internal_GetName(IntPtr)
255a283
> ProfilerMarker:    Void Internal_Emit(IntPtr, UInt16, Int32, Void*)
259,271c287,300
< PropertySceneHandle:    Boolean GetBoolInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertySceneHandle:    Boolean HasValidTransform(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertySceneHandle:    Boolean IsBound(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertySceneHandle:    Int32 GetIntInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertySceneHandle:    Single GetFloatInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertySceneHandle:    Void ResolveInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertyStreamHandle:    Boolean GetBoolInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertyStreamHandle:    Int32 GetIntInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertyStreamHandle:    Single GetFloatInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertyStreamHandle:    Void ResolveInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< PropertyStreamHandle:    Void SetBoolInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, Boolean)
< PropertyStreamHandle:    Void SetFloatInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, Single)
< PropertyStreamHandle:    Void SetIntInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, Int32)
---
> PropertySceneHandle:    Boolean GetBoolInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertySceneHandle:    Boolean HasValidTransform(UnityEngine.Animations.AnimationStream ByRef)
> PropertySceneHandle:    Boolean IsBound(UnityEngine.Animations.AnimationStream ByRef)
> PropertySceneHandle:    Int32 GetIntInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertySceneHandle:    Single GetFloatInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertySceneHandle:    Void ResolveInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Boolean GetBoolInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Boolean GetReadMaskInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Int32 GetIntInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Single GetFloatInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Void ResolveInternal(UnityEngine.Animations.AnimationStream ByRef)
> PropertyStreamHandle:    Void SetBoolInternal(UnityEngine.Animations.AnimationStream ByRef, Boolean)
> PropertyStreamHandle:    Void SetFloatInternal(UnityEngine.Animations.AnimationStream ByRef, Single)
> PropertyStreamHandle:    Void SetIntInternal(UnityEngine.Animations.AnimationStream ByRef, Int32)
285,286d313
< ResourceContext:    Void Internal_FreeArray(Void*, Void*)
< ResourceContext:    Void* Internal_AllocateArray(Void*, Int32)
303a331
> TransformAccess:    Void GetLocalToWorldMatrix(UnityEngine.Jobs.TransformAccess ByRef, UnityEngine.Matrix4x4 ByRef)
305a334
> TransformAccess:    Void GetWorldToLocalMatrix(UnityEngine.Jobs.TransformAccess ByRef, UnityEngine.Matrix4x4 ByRef)
313,329c342,377
< TransformSceneHandle:    Boolean HasValidTransform(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformSceneHandle:    UnityEngine.Quaternion GetLocalRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformSceneHandle:    UnityEngine.Quaternion GetRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformSceneHandle:    UnityEngine.Vector3 GetLocalPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformSceneHandle:    UnityEngine.Vector3 GetLocalScaleInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformSceneHandle:    UnityEngine.Vector3 GetPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    UnityEngine.Quaternion GetLocalRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    UnityEngine.Quaternion GetRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    UnityEngine.Vector3 GetLocalPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    UnityEngine.Vector3 GetLocalScaleInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    UnityEngine.Vector3 GetPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    Void ResolveInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef)
< TransformStreamHandle:    Void SetLocalPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, UnityEngine.Vector3)
< TransformStreamHandle:    Void SetLocalRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, UnityEngine.Quaternion)
< TransformStreamHandle:    Void SetLocalScaleInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, UnityEngine.Vector3)
< TransformStreamHandle:    Void SetPositionInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, UnityEngine.Vector3)
< TransformStreamHandle:    Void SetRotationInternal(UnityEngine.Experimental.Animations.AnimationStream ByRef, UnityEngine.Quaternion)
---
> TransformSceneHandle:    Boolean HasValidTransform(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    UnityEngine.Quaternion GetLocalRotationInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    UnityEngine.Quaternion GetRotationInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    UnityEngine.Vector3 GetLocalPositionInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    UnityEngine.Vector3 GetLocalScaleInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    UnityEngine.Vector3 GetPositionInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformSceneHandle:    Void GetGlobalTRInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3 ByRef, UnityEngine.Quaternion ByRef)
> TransformSceneHandle:    Void GetLocalTRSInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3 ByRef, UnityEngine.Quaternion ByRef, UnityEngine.Vector3 ByRef)
> TransformStreamHandle:    Boolean GetPositionReadMaskInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    Boolean GetRotationReadMaskInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    Boolean GetScaleReadMaskInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    UnityEngine.Quaternion GetLocalRotationInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    UnityEngine.Quaternion GetRotationInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    UnityEngine.Vector3 GetLocalPositionInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    UnityEngine.Vector3 GetLocalScaleInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    UnityEngine.Vector3 GetPositionInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    Void GetGlobalTRInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3 ByRef, UnityEngine.Quaternion ByRef)
> TransformStreamHandle:    Void GetLocalTRSInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3 ByRef, UnityEngine.Quaternion ByRef, UnityEngine.Vector3 ByRef)
> TransformStreamHandle:    Void ResolveInternal(UnityEngine.Animations.AnimationStream ByRef)
> TransformStreamHandle:    Void SetGlobalTRInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3, UnityEngine.Quaternion, Boolean)
> TransformStreamHandle:    Void SetLocalPositionInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3)
> TransformStreamHandle:    Void SetLocalRotationInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Quaternion)
> TransformStreamHandle:    Void SetLocalScaleInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3)
> TransformStreamHandle:    Void SetLocalTRSInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3, UnityEngine.Quaternion, UnityEngine.Vector3, Boolean)
> TransformStreamHandle:    Void SetPositionInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Vector3)
> TransformStreamHandle:    Void SetRotationInternal(UnityEngine.Animations.AnimationStream ByRef, UnityEngine.Quaternion)
> TypeCache:    TypeCollection GetTypesDerivedFromInterface(System.Type)
> TypeCache:    TypeCollection GetTypesDerivedFromInterface(System.Type)
> TypeCache:    TypeCollection GetTypesDerivedFromType(System.Type)
> TypeCache:    TypeCollection GetTypesDerivedFromType(System.Type)
> TypeCollection:    System.Type GetValue(IntPtr, Int32)
> TypeCollection:    System.Type GetValue(IntPtr, Int32)
> TypeCollection:    Void Internal_CopyTo(System.Type[], Int32)
> TypeCollection:    Void Internal_CopyTo(System.Type[], Int32)
> TypeDB:    Byte[] SerializeToBinary()
> TypeDB:    Byte[] SerializeToBinary()
331a380,381
> TypeDB:    Void DeserializeFromBinary(Byte[])
> TypeDB:    Void DeserializeFromBinary(Byte[])

There are a few important things to keep in mind about the above changes. First, the reflection script finds functions regardless of their access level. This means it finds private functions that aren’t directly callable in C#. They can still be called though, but reflection is needed to make the call. While this is slower, if there’s some critically-important function then it may be worth the extra cost.

Second, and relatedly, there are likely ways of calling these functions indirectly. That is, there are probably public functions that our game code can call that will call these private functions for us. Exactly which functions these are is beyond the capabilities of the script, so some additional investigation is required. Decompilation may be a fruitful tool here.

Finally, many of the apparent changes are really just renaming. For example, many functions were “removed” from TransformSceneHandle but, suspciously, functions with identical signatures were added to TransformSceneHandle. These aren’t really “new” job-safe functions.

What’s important to your game is really game-specific, so it’s good to take a look through the above list to see if anything interesting catches your eye. Does your game or its tools make heavy use of reflection? If so, then the new functions in TypeCache, TypeCollection, and TypeDB might be really interesting. If not then you might be more interested in ProfilerMarker, NavMeshQuery, GUID, or Analytics.