Jobs that executed on the main thread with `Run()` and UnityEngine API

By the way, if you don’t care about Mono, interop cost, and you prefer the same API as Unity’s, you can use this project for native scripting which designed for C++ primarily. I’m just trying a different approach using possibilities of Burst to stay closer as possible to the engine, and I’m doing that primarily for C11.

I’m well aware of that project. Before Jobs and Burst I was looking for a solution to do raycasting against an animated mesh for thousands of raycasts, and so I played around a bit with C++ and C# interop. But for me, Jobs and Burst completely solved that problem, so I haven’t investigated much how interop plays with Burst.

Apologies in advance if I completely missed the point, but my understanding is that you are using Burst to generate access points to the Unity API from a native context, and by having a Burst job call into your native code, you can hook into the Burst-generated header to talk to Unity APIs in a native context.

However, your simple test cases have only tested struct types and static methods, things that Burst can find in a native context and bring in for you. But you stated that you needed to talk to GameObjects and Components, since that is what you are using for presentation. Those are managed data types with an additional native Unity backend. That may give Burst a lot of trouble. You can’t test those right now because of this main-thread lockout issue. But what you can do is test a Thread-Safe managed type with a native Unity backend.

So if you get Burst to access AnimationCurve methods without interop, then really the only thing blocking you is the main thread issue. But my suspicion is that Burst is going to start screaming at you when you try to work with AnimationCurve or GameObjects.

Yes, this is correct. As I said any managed type is just how Unity abstracted native functionality, but nothing stops you to change that, we have at least two options. I already did that with Debug.Log() for example, which again, you can’t use within a burstified job in Unity with the original API.

Thanks. I totally missed that with Debug.Log. Also that means that you figured out how to Debug.Log in a Burst job.

But I am apparently wrong about something. I didn’t think Burst would generate access points to things like Debug.Log, so either I am wrong about that or you found a different way to access those native functions in a native context. In the latter case, I don’t understand what the benefit is of using Burst to launch your native context versus just launching your own (and potentially avoiding the thread-lock issue). The overhead of Run() is 6-7 microseconds on my rig, so its not like it magically removes the initial interop cost.

Anyways, if you don’t feel like explaining this to my incompetent self, don’t bother. Besides Debug.Log and Playables, any use case I would have for this (live syncing with other applications) I could just build the binding in the other direction.

No problem, I think it would be easier to understand everything if I just release the code or write an article with all the details. If you measure anything out of burstified jobs in Unity, you measure it with Mono, as well as the Run() itself which doesn’t matter since we manipulate pointers and entry points natively.

@JakeTurner I’ve tried to add [NativeProperty(IsThreadSafe = true)] and [ThreadAndSerializationSafe] to the Time.deltaTime similarly to other API using the assembly editor and the exception is still there.

After some digging found that Unity’s runtime is checking a thread ID using GetCurrentThreadID() on Windows. Assuming that an ID of the main thread is assigned statically at initialization using the same function, this shouldn’t be an issue. Here’s another script for reproducing without Burst:

using System.Runtime.InteropServices;
using Unity.Entities;
using Unity.Jobs;
using UnityEngine;

public class ThreadIDTestSystem : JobComponentSystem {
    [DllImport("kernel32.dll")]
    public static extern uint GetCurrentThreadId();

    private static uint threadID;

    private struct CreateJob : IJob {
        public void Execute() {
            threadID = GetCurrentThreadId();

            var deltaTime = Time.deltaTime;
        }
    }

    protected override void OnCreate() {
        var createJob = default(CreateJob);

        createJob.Run();

        if (threadID == GetCurrentThreadId())
            Debug.Log("Thread ID is the same!");
        else
            Debug.Log("Thread ID is not the same!");
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies) {
        return inputDependencies;
    }
}