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.
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.
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.
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 ?
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.
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.
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.
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
ok yes, my jobs were going from 2ms to 200ms
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.
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
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?