Search Unity

Resolved 'Exception: This method should have been replaced by source gen.' for IJobEntity in static method

Discussion in 'Entity Component System' started by Heptagram064, Apr 15, 2023.

  1. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Hello,

    When i was trying to query a bunch of entities with IJobEntity, everything worked fine. It just clogged the readability of my ISystem. For this i thought it a good idea to move my query function outside of my ISystem file.

    I mean, moving the ISystem code block to a normal function in the same file works.

    However, if i try to move it to a static namespace, this gives me the error 'Exception: This method should have been replaced by source gen.', pointing towards the myJob.Run() part of the function.

    Does anyone know how to properly move a ISystem code block calling a Job, to a static namespace?

    And if it is really forbidden to move myJob.Run() calls to a static method, what would be the better way to separate such ISystem code blocks into another file?
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    For now it's not possible, SystemAPI can't get system context in static context. I've asked Unity to add something like
    SystemAPI.WithContext(SystemHandle\ISystem)
    for allowing to create static utilities. But not promises yet.
     
  3. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    You don't happen to know any other method (then a static context) to split a function out of a file / current context (that allows SystemAPI/Jobs to work)?
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Everything that SystemAPI does can be expressed otherwise (e.g. by accessing lookup structs).
    It depends on the context of what you're trying to achieve.

    Stuff that directly triggers codegen cannot be moved, because as mentioned, that prevents codegen from running. (.Run(), .Schedule(), .ScheduleParallel() etc);

    However, moving logic to the separate IJobEntity and re-using it is completely valid strategy.
    If you want abstraction layer to pass around into jobs, you could use real Aspects, or "fake" aspects. E.g.:
    Code (CSharp):
    1. // Pseudo-code
    2. public static struct FakeAspect {
    3.     [ReadOnly]
    4.     public ComponentLookup<T1> SomeDataLookup;
    5.     public BufferLookup<T2> SomeBufferLookup;
    6.  
    7.     public static FakeAspect FromState(ref SystemState state) {
    8.         // Can be filled by anything to reduce typing
    9.         return new FakeAspect {
    10.              SomeDataLookup = state.GetComponentLookup<T1>(true), // true for readonly
    11.              SomeBufferLookup = state.GetBufferLookup<T2>()
    12.        };
    13.     }
    14. }
    15.  
    16. // Then in system you'd use it like:
    17. ...
    18.  
    19. OnUpdate (ref SystemState state) {
    20.     FakeAspect aspect = FakeAspect.FromState(ref state);
    21.     SomeJob job = new Job {
    22.        Aspect = aspect,
    23.     };
    24.     job.Run(); // Or schedule or anything else
    25. }
    Basically encapsulating implementation of what you need for job to execute into separate structure. There's a con though, that you'd probably overuse it requesting components you don't actually need. But its a small price to pay keep typing to the minimum.
     
    Last edited: Apr 17, 2023
  5. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    This is exactly the one thing i hoped to be able to move around, because its the only thing that clutters my scripts a little. Any chance they will be adding the ability to call .Run(), .Schedule(), Etc, from anywhere outside of a ISystem {} or SystemBase {} (ofcorse with a state reference) eventually?
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I mean, its just one line, how bad does it look?

    And in general its a good thing to have for people to understand what's going on in your codebase. Plus keep logic inside systems rather than hide expensive operations somewhere else.

    I doubt UT will run full assembly scan to find exact calls. That's a non-trivial task in terms of performance.
     
  7. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Maybe i am just very greedy in terms of line count, but in MonoBehaviour workflow i always tried to condense all often used logic used in a system to 1 line. e.g.
    Objects objects = UtilityClass.GetAllObjectsWithAttributes(myAttributes)
    , or
    Vector4[] complicatedMathResult = UtilityClass.DoComplicatedMath(prerequisiteOne, prerequisiteTwo, prerequisiteThree)
    Where the UtilityClass is tucked away in a file somewhere else.

    Jobs makes this at the very cheapest a 3 line thing with e.g.
    Code (CSharp):
    1. NativeArray<Vector4> result = new NativeArray<Vector4>(Allocator.TempJob);
    2. new ComplicatedMathJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();
    3. result.Dispose();
    of course the 3rd line being memory management, which makes sense for the DOTS benefits.

    Thing is, i can move this outside the OnUpdate(), as long as its still in the ISystem {} block.
    Code (CSharp):
    1.         /// <summary>
    2.         /// <para>Runs a job that does complicated math</para>
    3.         /// </summary>
    4.         private Vector4[] DoComplicatedMath(DataType prerequisiteOne, DataType prerequisiteTwo, DataType prerequisiteThree, ref SystemState state)
    5.         {
    6.             // Create some natives
    7.             NativeList<Vector4> nativeMath = new NativeList<Vector4>(Allocator.TempJob);
    8.             // Do the actual math
    9.             new ComplicatedMathJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, nativeMath).Run();
    10.             // Convert the natives
    11.             Vector4[] result = UtilitiesNotClassButStructIguess.ConvertFromNative(nativeMath);
    12.             // Dispose the natives
    13.             nativeMath.Dispose();
    14.             // Return the result
    15.             return result;
    16.         }
    Enabling me to do things like
    Code (CSharp):
    1. Vector4[] complicatedMathResult = DoComplicatedMath(prerequisiteOne, prerequisiteTwo, prerequisiteThree, ref state)
    , which is one line i can put wherever i need.

    The problem here is, for each unique function, my ISystem {} block grows thicker. Personally i dont see the difference between having my function be inside a ISystem {} block, or some other files utility struct, but that might just be my limited understanding of C# and ECS
    I guess in the end, the overhead is not that big, as you can declare your native prerequisites (or fake aspect) once, then run and use the job a couple times, to dispose it at the bottom of OnUpdate().
    Code (CSharp):
    1. NativeList<DataType> result = new NativeList<DataType>(Allocator.TempJob)
    2.  
    3. new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();
    4.  
    5. DoThingsWithResult(result);
    6.  
    7. prerequisiteOne = ChangePrerequisiteOne(prerequisiteOne);
    8. prerequisiteTwo = ChangePrerequisiteTwo(prerequisiteTwo);
    9. prerequisiteThree = ChangePrerequisiteThree(prerequisiteThree);
    10.  
    11. new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();
    12.  
    13. DoThingsWithResult(result);
    14.  
    15. prerequisiteOne = ChangePrerequisiteOne(prerequisiteOne);
    16. prerequisiteTwo = ChangePrerequisiteTwo(prerequisiteTwo);
    17. prerequisiteThree = ChangePrerequisiteThree(prerequisiteThree);
    18.  
    19. new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();
    20.  
    21. DoThingsWithResult(result);
    22.  
    23. result.Dispose();
    For which calling the job is like calling a function.
     
    Last edited: Apr 18, 2023
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    There are a couple of things to simplify this code.

    - Get rid of native <-> managed conversion. It is incredibly slow.
    If you want persistent data storage - use Allocator.Persistent with any native collection.
    This will also allow you to:

    - Encapsulate logic inside jobs or data inside data structs instead of methods.
    Use short static methods for the math logic instead of one bulky static method that runs something. Otherwise you'll inevitably get side effects all over the place in case if something changes later on.

    - Instead of passing and receiving back new struct (which I'd assume prerequisiteX are) - use ref keyword instead of returns. Native containers are referenced by default, so no need to ref or return them at all. They're pointers to the memory blocks (if simplified).

    - Chain jobs instead of runing them one by one. Schedule > Run. No need to waste main thread time.
    You can also dispose after job is done by using .Dispose(*jobHandle*);

    - Store prerequisites in a single struct and pass it around instead. This will simplify parameters.
     
    Last edited: Apr 18, 2023
  9. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Some good advises, thx

    Think i forgot to mention i am still new at ECS :D,
    I will mark this thread as resolved, as i dont think more comments will provide further value for the sake of the original question.
     
    xVergilx likes this.