Search Unity

Can you give a job a lamda/func and still use burst etc?

Discussion in 'Burst' started by Cell-i-Zenit, Jan 8, 2020.

  1. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Hi,

    so i have a job which accesses a static method.

    I want to refactor this and provide a different method depending on some circumstances.

    So the original job was like this:

    Code (CSharp):
    1. public void Execute(){
    2.     var s = FooClass.GetXXXX();
    3.     //do something with s
    4. }
    i now would like to do it "somehow" like this:

    Code (CSharp):
    1. public struct DynamicJob: IJob{
    2.  
    3.     private Func<float> _func;
    4.     public DynamicJob(Func<float> lambda){
    5.         _func = lambda;
    6.     }
    7.     public void Execute(){
    8.         var s = _func.Invoke();
    9.         //do something with s
    10.     }
    11. }
    Any idea if something like this is possible? I remember that they talked about this in an earlier Entity package version but i cannot find that.
     
  2. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Have a look at this thread and see if it helps you
     
    Cell-i-Zenit likes this.
  3. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Exactly what i was looking for thanks
     
  4. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    287
    GilCat and DwinTeimlon like this.
  5. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Hi,

    after looking at the docu i tried to make a "function pointer" dictionary available for jobs.

    Code (CSharp):
    1. public abstract class MutableStaticTest
    2.     {
    3.         public static readonly SharedStatic<NativeHashMap<int, FunctionPointer<SimplePositionDelegate>>> HeightPointers
    4.             = SharedStatic<NativeHashMap<int, FunctionPointer<SimplePositionDelegate>>>
    5.                 .GetOrCreate<MutableStaticTest, HeightPointersKey>();
    6.  
    7.         private class HeightPointersKey { }
    8.     }
    Since the HeightMethods cannot be static i need to use the way provided by Gilcat:

    Code (CSharp):
    1. MutableStaticTest.HeightPointers.Data.Add(world, new FunctionPointer<SimplePositionDelegate>(
    2.                 Marshal.GetFunctionPointerForDelegate((SimplePositionDelegate) worldSetting.GetHeightFactor)));
    But this throws this error here:

    NullReferenceException: Object reference not set to an instance of an object
    Unity.Collections.LowLevel.Unsafe.UnsafeHashMapBase`2[TKey,TValue].TryGetFirstValueAtomic (Unity.Collections.LowLevel.Unsafe.UnsafeHashMapData* data, TKey key, TValue& item, Unity.Collections.NativeMultiHashMapIterator`1[TKey]& it) (at Library/PackageCache/com.unity.collections@0.4.0-preview.6/Unity.Collections/UnsafeHashMap.cs:645)
    Unity.Collections.LowLevel.Unsafe.UnsafeHashMapBase`2[TKey,TValue].TryAdd (Unity.Collections.LowLevel.Unsafe.UnsafeHashMapData* data, TKey key, TValue item, System.Boolean isMultiHashMap, Unity.Collections.Allocator allocation) (at Library/PackageCache/com.unity.collections@0.4.0-preview.6/Unity.Collections/UnsafeHashMap.cs:460)
     
  6. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Why do you want to give bust static support to your function pointers? You are just going to feed them to your jobs and they will be burst compiled there.
    Just create a regular dictionary with those and add your favorite pointers:
    Code (CSharp):
    1. public static Dictionary<int, FunctionPointer<SimplePositionDelegate)>> HeightPointers = new Dictionary<int, FunctionPointer<SimplePositionDelegate)>>();
    2.  
    BTW the error you have there is because you are trying to add data to an unallocated NativeHashMap.
     
  7. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    It looks like there can happen alot of bad things with burst. so i try to explain what iam trying to do and you tell me the best way of doing that :p

    I have 10 Jobs, each needing a specific Value to do their work. This Value can only be calculated with a method.

    In my game the user can switch out the methods. So in one moment the method/formula is 1 + x and in another moment it could be 2 + x or x * x * 10.

    So my idea is that i have all the possible Methods inside a Hashmap and just provide this hashmap to each job (in my case via this static hashmap construct).

    When each job runs i just point to the correct implementation choosen by the user (with the hashmap key) and it should just work.

    Its also important that this method is used ALOT each frame (~ 10k times sometimes)

    EDIT: before this static version, whenever i created a job i created the FunctionPointers. I didnt really like that and thought its not that clean to do that every frame

    EDIT2: when i have a static Dictionary and just fill the functionpointers with it and in the constructor of the job i assign these function pointers to a local functionpointer var. It crashes unity without any errormessage when i execute the jobs :(
     
    Last edited: Jan 11, 2020
  8. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I would have a dictionary of those function pointers then supply the correct function pointer to you job (based on some game settings as key i guess).
    Can you share an example of that?
     
  9. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290

    This is exactly what iam doing.

    Code (CSharp):
    1. public static readonly Dictionary<int, FunctionPointer<SimplePositionDelegate>> HeightFunctionPointers =  new Dictionary<int, FunctionPointer<SimplePositionDelegate>>();
    I fill it with some function pointers:

    Code (CSharp):
    1. var functionPointer = new FunctionPointer<SimplePositionDelegate>
    2. (Marshal.GetFunctionPointerForDelegate((SimplePositionDelegate) classA.Foo));
    Inside my jobs constructor:

    Code (CSharp):
    1. FunctionPointer= DataSystem.HeightFunctionPointers[0];
    I call the method like this inside a job:

    Code (CSharp):
    1. abc = FunctionPointer.Invoke(ref position);
    EDIT: it has something todo with burst as when i disable burst compilation it works fine

    EDIT2: it looks like there is a bug on my side because the function pointers are null. Burst shouldnt crash unity in this case but yeah.. I will report a bug once i can pin it down
     
    Last edited: Jan 11, 2020
  10. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Are you sure it's not the static readonly Dictionary? You can only assign that as part of the declaration on the the class constructor. I would expect some kind of exception or compile error if changed from somewhere else.
     
  11. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    I tried it out in linqpad: i can add stuff to a readonly dictionary fine as long as i initialized it inside a constructor

    EDIT: well the whole thing is pretty unstable. If i create a new function pointer each time the job executes it works fine, but if i precompile it, it will crash often without any warning.

    I suspect it is because i get the function from an instance variable and this instanceis getting garbage collected or moved in memory or something like this.
     
    Last edited: Jan 12, 2020
  12. sheredom

    sheredom

    Unity Technologies

    Joined:
    Jul 15, 2019
    Posts:
    300
    I'm suspicious about the static readonly dictionary too - if you are able to get me the optimized LLVM output from the Burst inspector for this job it'd help me double check if that is an issue or not (can DM me it if you want to keep your secret sauce!)
     
  13. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    I will do that.

    I can also see if a non readonly variant is working better, or if a non static version is working without any crashes
     
  14. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    I made a small test to see if it is really because of the static readonly Dictionary:

    Code (CSharp):
    1. private FunctionPointer<SimplePositionDelegate> _functionPointer;
    2.         private bool FunctionPointerSet;
    3.        
    4.         public FunctionPointer<SimplePositionDelegate> GetFunctionPointer()
    5.         {
    6.             if (FunctionPointerSet)
    7.             {
    8.                 return _functionPointer;
    9.             }
    10.             else
    11.             {
    12.                 //FunctionPointerSet = true; //this flag here
    13.                 _functionPointer = new FunctionPointer<SimplePositionDelegate>(Marshal.GetFunctionPointerForDelegate(
    14.                     (SimplePositionDelegate) _worldType.GetHeightFactor));
    15.                 return _functionPointer;
    16.             }
    17.         }
    My Job executes "GetFunctionPointer" inside the constructor, whenever i execute the job.

    So the test is just requesting the newest function pointer each time i run the job (i just commented out the flag).

    The job runs without any problems.

    I now uncomment the code and it hangs the second time i run the job(since its the first time i submit the "stored" function pointer).

    I can provide the complete source if you want but then i need to submit it in private
     
    sheredom likes this.
  15. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    287
    You need to allocate a GCHandle for the delegate, otherwise it can be garbage-collected by the runtime - which sounds like what is happening.

    Something like this:

    Code (CSharp):
    1. private GCHandle _delegateHandle;
    2. private FunctionPointer<SimplePositionDelegate> _functionPointer;
    3. private bool FunctionPointerSet;
    4.  
    5. public FunctionPointer<SimplePositionDelegate> GetFunctionPointer()
    6. {
    7.     if (FunctionPointerSet)
    8.     {
    9.         return _functionPointer;
    10.     }
    11.     else
    12.     {
    13.         FunctionPointerSet = true; //this flag here
    14.         var delegate = (SimplePositionDelegate) _worldType.GetHeightFactor;
    15.         _delegateHandle = GCHandle.Alloc(delegate);
    16.         _functionPointer = new FunctionPointer<SimplePositionDelegate>(Marshal.GetFunctionPointerForDelegate(delegate));
    17.         return _functionPointer;
    18.     }
    19. }
    Note that I'm not calling Free() on the GCHandle - I don't know if you have a Dispose() or somewhere similar that you could put that.
     
    GilCat and Cell-i-Zenit like this.
  16. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    This works thank you.

    Important here is that you need to alloc the delegate, and also use this delegate as functionpoint parameter.