Is it possible to change booleans and floats inside of a monodevelop from a job struct?

I’ve been messing around learning about ECS and the Job system and I think for what I want to do the Job system is potentially the best for me since I don’t have to hugely re-write everything the way I would if I were to use ECS. There’s just one problem though, my plan is to use the Job system to optimise this incredible large script that I have which dictates the behaviour of about 100 villagers per village in a game that I’m making and it’s almost 650 lines of code.

I knew I was potentially going to have to re-write this anyway but I was hoping I could avoid having to completely re-write the whole script just for the job system and re-write portions of it that I know are going to use up the most computing power and cause FPS problems. In order to do that though I need to be able to change booleans that exist in the main monodevelop within the job structs after something happens so I can update other much less intensive processes that just change UI text for example.

I guess the short version of all of this is, am I screwed?

Bad News
You can not work directly with them. They need to be passed to the job. The reason jobs are so efficient is because they only work on the data supplied to them in the form of blittable types - although there is a special job type that can interate over transform components (IJobParrallelForTransform).

public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

Good News
You can change bools/floats in a monobehaviour script but you will have to do it like this.

Job

public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

Schedule Job

public float someFloat;

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

JobHandle handle = jobData.Schedule();

handle.Complete();

someFloat = result[0];

result.Dispose();

Not sure if that would resolve your issue. If not can you explain a bit more?

Jobs can not work with reference types and must be passed data to work on rather than it taking it from pre-existing member variables. However, as pointed out above you can technically change a blittable type that resides in a monobehaviour by injecting the result of a job.

Thank you, this was actually the perfect answer believe it or not because I was really just looking to see what my options were. It seems that simply re-writing all my functions as structs etc. will be a much better option in the long run, will take some doing but given the results the job system gives it will probably be worth it.

I guess the only question I have now is if I declare a boolean within a struct, how will I get it from elsewhere? For instance if I’ve got a monodevelop on another script that needs to access the boolean in the job how would I get that? Still wrapping my head around all this new syntax.

Jobs work in a fairly specific way (in order to get the most performance and safe guard against multithreading issues) and this dictates what they are good for (and what they are not good for too).

Example 1
Jobs are not meant to perform menial tasks. In fact, in certain cases actually scheduling the job and completing it will take more resources/time than performing the same calculation on the main thread.

BadJob.cs

public struct WasteOfTime : IJob
{
    public float a;
    public float b;

    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

Bad.cs

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

MyJob jobData = new MyJob();

jobData.a = 10;
jobData.b = 10;

jobData.result = result;

JobHandle handle = jobData.Schedule();

handle.Complete();

Debug.Log(result[0]);

result.Dispose();

Example 2
A good use for a job might be converting seconds to hours, minutes, and seconds.

ConvertSecondsToHoursMinutesSecondsJob.cs

public struct ConvertSecondsToHoursMinutesSecondsJob : IJob
{
    public int seconds;

    public NativeArray<float> result;


    public void Execute()
    {
        // convert given number of seconds to hours, minutes, and seconds HH:MM:SS
    }
}

ConvertSecondsToHoursMinutesSeconds.cs

public int hours;
public int minutes;
public int seconds;

NativeArray<int> result = new NativeArray<float>(3, Allocator.TempJob);

ConvertSecondsToHoursMinutesSecondsJob jobData = new ConvertSecondsToHoursMinutesSecondsJob ();

jobData.seconds = 3730   // 1 hour, 2 minutes, 10 seconds

jobData.result = result;

JobHandle handle = jobData.Schedule();

handle.Complete();

hours = result[0];
minutes = result[1];
seconds = result[2];

result.Dispose();

That’s not how it works.

The data inside the struct is special. You’re not allowed to touch it with your dirty, sticky, unoptimized hands. However, you can pass it data and kindly ask a job to tell you the result.

This result can then be passed back into a “normal” float/bool/string and then you can use it.

ConvertSecondsToHoursMinutesSecondsJob.cs

public struct ConvertSecondsToHoursMinutesSecondsJob : IJob
{
    // You can not reference this! When you create a job you are essentially
    // creating a copy of this struct in order to pass it data.
    public int seconds;

    // You need to store the result in a native array (doesn't have to be called result)
    // This is how you will access the data when the job has finished.
    public NativeArray<float> result;


    public void Execute()
    {
        // convert given number of seconds to hours, minutes, and seconds HH:MM:SS
    }
}

ConvertSecondsToHoursMinutesSeconds.cs

// setup "normal" variables to store the results of a job
public int hours;
public int minutes;
public int seconds;

// setup a native array that will be passed to the job
NativeArray<int> result = new NativeArray<float>(3, Allocator.TempJob);

// job data
ConvertSecondsToHoursMinutesSecondsJob jobData = new ConvertSecondsToHoursMinutesSecondsJob ();

// give it some data
jobData.seconds = 3730   // 1 hour, 2 minutes, 10 seconds

// pass the native array
jobData.result = result;

// schedule the job
JobHandle handle = jobData.Schedule();

// complete it
handle.Complete();

// get the result of the calculation and put it back into the "normal" variables for later use
hours = result[0];
minutes = result[1];
seconds = result[2];

// get rid of it now because we don't need it anymore
result.Dispose();

A few things to note:

  • You should schedule jobs as early as possible, and only complete as late as possible. Completing in LateUpdate is common. However, TempJobs get 4 frames. So you can “wait” a maximum of 3 entire frames to complete a job for max performance.

  • If you are only passing data to a job it can be marked as ready only. This tells the job that it will only be reading that data, not writing to it. In the above example, the seconds input could be marked as ready only as the job would never overwrite the provided seconds (kind of like a static modifier)

  • You can use IJobParrallelForTransform for rotating or translating gameobject transforms. For example, you can use it for world origin shifting.

ExampleJob.cs

public struct ExampleJob : IJob
{
    public int numberA;
    public int numberB;

    public NativeArray<bool> result;


    public void Execute()
    {
        if (numberA == numberB)
        {
            result[0] = true;
        }
    }
}

ExampleScript.cs

public bool someBoolean;

public void SomeMethod()
{
   

    NativeArray<bool> result = new NativeArray<bool>(1, Allocator.TempJob);

    ExampleJob jobData = new ExampleJob();

    jobData.numberA = 10;
    jobData.numberB = 10;

    jobData.result = result;

    JobHandle handle = jobData.Schedule();

    handle.Complete();

    someBoolean = result[0];
  
    result.Dispose();
}

SecondScript.cs

public ExampleScript exampleScript;

public bool anotherBoolean;

exampleScript.SomeMethod();

anotherBoolean = exampleScript.someBoolean;

It might need to be optimised more in terms of when the job is completed… but that would probably work. Call a method from the other script that initiates a job.

Thank you that definitely makes sense and actually I may be able to save some time, instead of switching up the way my functions are working I can simply put the code that I can convert to the job system in their own separate jobs and have that run off update as normal without too much fuss.

That saved my arse thanks lol, I was worried looking at ECS even with the job system I was going to have to re-write hundreds of lines of code, helped make my decision for me. I guess this is where the unity profiler comes in. I’m going to have to start investigating where the most wastage is happening and deal with things that way and see how it affects performance.