reusing functions inside jobs

how would i define a function that i can use in different jobs ?

for example i have an algorithm that calculates the height of a terrain i’m generating.
so i use this for example for calculating vertex positions of a mesh(tile).

now i would like to use the same algorithm to get the height on a specific position, for collision checking.

is it possible to define a static ? height() function that i can use in different jobs ?
what if this function requires a lookup table, like a perlin hash table.

right now i pass the lookuptable as a nativearray to a job and do all the calculation in the job without a sub-function. but if i could reuse the algorith it would make things easier than copying the same code to each job.

thanx for any suggestions,
Rob

Yes and yes.
But if you can for example define system, which calculates height and access with accordingly tagged entities ,rather than static method, I think would be better. Depends on application.

1 Like

I’m not using entities, just the jobsystem,
basically i would just need a helper function that i pass some parameters and returns a height value.

so i would get the height for a big array of position to create mesh in an iparalelfor job, or just get the height for single position in a ijob if i want to check where the floor is for example

You can create a static method which doesn’t use and refer to any objects or managed arrays if you want to benefit from [BurstCompiler]. You will have to pass every value or e.g. nativearray you want to process.

For convience, I always create a non-static / object variant of the method which basically wraps the static method. I use that instantiated method wrapper for on demand / direct calls in normal methods (not inside jobs) so I don’t have to pass all arguments over and over again.

1 Like

thanx for the suggestions, I have it working also with a static readonly lookup table.

@meanmonkey : could you elaborate a bit on your technique for wrapping the static function ? I have for example an animationcurve that i sample into 512 values and pass to my jobs as a native array, but it would be convenient if i would not have to pass all argument all the time as you suggest ?

thanx!

Sure:

 public class SomeClass
        {
            public NativeArray<int> array;
            public int value1;
            public int value2;
            public int value3;

            public SomeClass()
            {
             
            }

            public int GetData()
            {
                return GetData_Static(value1, value2, value3, array);
            }

            public static int GetData_Static(int _value1, int _value2, int _value3, NativeArray<int> _array)
            {
                return _array[_value1 + _value2 + _value3];
            }
        }

You can use GetData_Static(args) in your jobs and GetData() for convinience.

An alternative would be using static fields or a static class at all, but the burst compiler will only be happy if static fields are readonly. But as readonly fields in static classes have to be initialized in the static constructor, you can’t init your data on demand at object creation which can be pretty unconvinient.

i see, thank you…!

job performance has completely dropped through the floor, is this because of the static lookup tables ?
how would i use a static getSample function as below in a IJobParallelFor job ?

public struct HeightFunctions {
    public readonly static int[] height_hash = {
151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
    };
    public readonly static int height_hash_mask = 255;

    public readonly static Vector2[] gradients2D = {
        new Vector2( 1f, 0f), new Vector2(-1f, 0f), 
        new Vector2( 0f, 1f), new Vector2( 0f,-1f),
        new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized, 
        new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
    };
    public readonly static int gradientsMask2D = 7;

    public static float getSample(float xf, float yf, int xi, int yi) {
        float value = 0f;
        float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
        float xn = xf - xi + unskew;
        float yn = yf - yi + unskew;

        float f = 0.5f - xn*xn - yn*yn;
        if (f > 0f) {
            float f2 = f * f;
            float f3 = f * f2;
            Vector2 g = gradients2D[height_hash[height_hash[xi & height_hash_mask] + yi & height_hash_mask] & gradientsMask2D];
            float v = g.x* xn + g.y * yn;
            value = v * f3;
        }
        return value;
    }
}

Replace your managed arrays to nativearrays and create a IJobParallelFor and tag it with [BurstCompile] (note that native arrays + burst is only efficient in a build, not in editor).

something like this (just hacked it down so it can be error prone):

using UnityEngine;
using Unity.Jobs;
using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Entities;
using Unity.Burst;

// =================================================================== //
public class ParallelForJobTest {

    public readonly NativeArray<int> height_hash;
    public readonly int height_hash_mask;

    public readonly NativeArray<Vector2> gradients2D;
    public readonly int gradientsMask2D;

    public NativeArray<float> results;

    // ------------------------------------------------------------------- //
    public ParallelForJobTest() {

        height_hash = new NativeArray<int>( new int[] {
            151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
        }, Allocator.Persistent);

        height_hash_mask = 255;

        gradients2D = new NativeArray<Vector2>(new Vector2[]  {
        new Vector2( 1f, 0f), new Vector2(-1f, 0f),
        new Vector2( 0f, 1f), new Vector2( 0f,-1f),
        new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized,
        new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
        }, Allocator.Persistent);

        gradientsMask2D = 7;

        int myJobSize = 1000; // ??
        results = new NativeArray<float>(myJobSize, Allocator.Persistent);

        TestJob testJob = new TestJob()
        {
            _height_hash = height_hash,
            _height_hash_mask = height_hash_mask,
            _gradients2D = gradients2D,
            _gradientsMask2D = gradientsMask2D


        };
        JobHandle testJobHandle = testJob.Schedule(myJobSize, 1); // choose your batchcount wisely. the more work has to be done, the less batches you wann do per thread.
        testJobHandle.Complete();

      
    }
   
    // ------------------------------------------------------------------- //
    public void ShutDown()
    {
        height_hash.Dispose();
        gradients2D.Dispose();
    }

    // =================================================================== //
    [BurstCompile]
    struct TestJob : IJobParallelFor
    {
        //[NativeDisableParallelForRestriction] // Use this on nativearrays if you want to read/write non linear. You have to guarantee thread saftey by yourself!

        [ReadOnly]
        public NativeArray<int> _height_hash;
        public int _height_hash_mask;

        [ReadOnly]
        public NativeArray<Vector2> _gradients2D;
        public int _gradientsMask2D;
      
        [WriteOnly]
        public NativeArray<float> results;
       
        // ------------------------------------------------------------------- //
        public void Execute(int i)
        {
            float xf; float yf; int xi; int yi;  // I guess you want to use these in the batch, dunno your logic.

            float value = 0f;
            float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
            float xn = xf - xi + unskew;
            float yn = yf - yi + unskew;

            float f = 0.5f - xn * xn - yn * yn;
            if (f > 0f)
            {
                float f2 = f * f;
                float f3 = f * f2;
                Vector2 g = _gradients2D[_height_hash[_height_hash[xi & _height_hash_mask] + yi & _height_hash_mask] & _gradientsMask2D];
                float v = g.x * xn + g.y * yn;
                value = v * f3;
            }

            results[i] = value;
        }
    }
}

It’s efficient everywhere, even in editor, bursted job - performant in several times, in build this values just much more, because all editor safety checks excluded from build.

4 Likes

Thanx! Yes this was exactly how i was using the jobsystem, but i was wondering if i could make some functions,
so i could easily reuse the algorithm in different types jobs that need the same calculations, in stead of having each job contain just a copy of the same code.

you mean like this ?

using UnityEngine;
using Unity.Jobs;
using UnityEngine.Jobs;
using Unity.Collections;
using Unity.Entities;
using Unity.Burst;

// =================================================================== //
public class ParallelForJobTest {

    public readonly NativeArray<int> height_hash;
    public readonly int height_hash_mask;

    public readonly NativeArray<Vector2> gradients2D;
    public readonly int gradientsMask2D;

    public NativeArray<float> results;

    // ------------------------------------------------------------------- //
    public ParallelForJobTest() {

        height_hash = new NativeArray<int>( new int[] {
            151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148, 247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175, 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54, 65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64, 52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213, 119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104, 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157, 184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
        }, Allocator.Persistent);

        height_hash_mask = 255;

        gradients2D = new NativeArray<Vector2>(new Vector2[]  {
        new Vector2( 1f, 0f), new Vector2(-1f, 0f),
        new Vector2( 0f, 1f), new Vector2( 0f,-1f),
        new Vector2( 1f, 1f).normalized, new Vector2(-1f, 1f).normalized,
        new Vector2( 1f,-1f).normalized, new Vector2(-1f,-1f).normalized
        }, Allocator.Persistent);

        gradientsMask2D = 7;

        int myJobSize = 1000; // ??
        results = new NativeArray<float>(myJobSize, Allocator.Persistent);

        TestJob testJob = new TestJob()
        {
            _height_hash = height_hash,
            _height_hash_mask = height_hash_mask,
            _gradients2D = gradients2D,
            _gradientsMask2D = gradientsMask2D


        };
        JobHandle testJobHandle = testJob.Schedule(myJobSize, 1); // choose your batchcount wisely. the more work has to be done, the less batches you wann do per thread.
        testJobHandle.Complete();

     
    }
  
    // ------------------------------------------------------------------- //
    public void ShutDown()
    {
        height_hash.Dispose();
        gradients2D.Dispose();
    }

    // =================================================================== //
    [BurstCompile]
    struct TestJob : IJobParallelFor
    {
        //[NativeDisableParallelForRestriction] // Use this on nativearrays if you want to read/write non linear. You have to guarantee thread saftey by yourself!

        [ReadOnly]
        public NativeArray<int> _height_hash;
        public int _height_hash_mask;

        [ReadOnly]
        public NativeArray<Vector2> _gradients2D;
        public int _gradientsMask2D;
     
        [WriteOnly]
        public NativeArray<float> results;
      
        // ------------------------------------------------------------------- //
        public void Execute(int i)
        {
            float xf; float yf; int xi; int yi; // I guess you want to use these in the batch, dunno your logic.

            results[i] = GetSampe(xf, yf, xi, yi, _height_hash, _height_hash_mask, _gradients2D,);
        }
    }

    // ------------------------------------------------------------------- //
    public static float GetSampe(float xf, float yf, int xi, int yi, NativeArray<int> _height_hash, int _height_hash_mask, NativeArray<Vector2> _gradients2D, int _gradientsMask2D)
    {

        float value = 0f;
        float unskew = (xi + yi) * 0.2113249f; // * squaresToTriangles;
        float xn = xf - xi + unskew;
        float yn = yf - yi + unskew;

        float f = 0.5f - xn * xn - yn * yn;
        if (f > 0f)
        {
            float f2 = f * f;
            float f3 = f * f2;
            Vector2 g = _gradients2D[_height_hash[_height_hash[xi & _height_hash_mask] + yi & _height_hash_mask] & _gradientsMask2D];
            float v = g.x * xn + g.y * yn;
            value = v * f3;
        }

        return value;
    }
}
1 Like

exactly yes…thank you, i guess i have to really separate all data from the static functions.
although a single float or int is ok, i cannot declare a small array as a lookuptable in this static function ?

Not sure but I think you can’t declare arrays inside a job when using burst. You’ll have to try, but the burst debugger will tell you if he’s not happy :slight_smile:

ok yes, my jobs were going from 2ms to 200ms :confused:
but i understand it a bit better and i can clean up / reuse my code a bit now with some functions,
as long as i pass in all the data it needs, thanx for your help.

Not sure if that helps, but maybe try replace Vector2 with float2.

I think problem with static method in your case may be, you are copying NativeArray to it, on each iteration, when accessing method.
Depends how big arrays are, this may cause some potential hiccups.
But I may be wrong for your case.

/\ This

Ignore the fact, it is ECS.
No burst
View attachment 333718

With burst
View attachment 333721

1 Like

performance is the same for me with static function as with direct inlining the code, i dont think there is a copy made for nativearray

from

from 2 to 200ms, that sounds wrong somehow. But I dunno what you are doing else in your code and what your start/stop point of time measure is. Also note that nativearrays and jobs are much slower in the editor due to safety checks which won’t occur in the final build. https://discussions.unity.com/t/692216

The best performance you get with nativearrays is always combined with burst jobs. Also note that native arrays outside burst jobs + mono are slower than managed arrays + mono, but you can use il2cpp if feasable for you, this will compensate the performance loss. https://discussions.unity.com/t/692345/4

1 Like

Im reviving this thread as i have the same Question. I want to reuse code between Jobs.
So for example i have seperate jobs for

  • Get Populated Area Job from 2D Grid
  • Get Indeces of Items from Native List Job
  • and so on.

Now in one case i need all these functionality in one job instead of creating all these smaller jobs in order.

So i want to use static/extension methods which will work on Native Containers or blittable types (int, bool) etc to reuse code between jobs.

So is this still the right Strategy in end of 2019? or is there a better approach?
Thanks

Besides calling static (and also struct) methods, I see Burst now supports function pointers. I haven’t tried them out yet, but might be of interest to you?

https://docs.unity3d.com/Packages/com.unity.burst@1.2/manual/index.html#function-pointers

1 Like