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

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?

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.

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)?

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.:

// Pseudo-code
public static struct FakeAspect {
    [ReadOnly]
    public ComponentLookup<T1> SomeDataLookup;
    public BufferLookup<T2> SomeBufferLookup;

    public static FakeAspect FromState(ref SystemState state) {
        // Can be filled by anything to reduce typing
        return new FakeAspect {
             SomeDataLookup = state.GetComponentLookup<T1>(true), // true for readonly
             SomeBufferLookup = state.GetBufferLookup<T2>()
       };
    }
}

// Then in system you'd use it like:
...

OnUpdate (ref SystemState state) {
    FakeAspect aspect = FakeAspect.FromState(ref state);
    SomeJob job = new Job {
       Aspect = aspect,
    };
    job.Run(); // Or schedule or anything else
}

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.

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?

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.

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.

NativeArray<Vector4> result = new NativeArray<Vector4>(Allocator.TempJob);
new ComplicatedMathJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();
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.

        /// <summary>
        /// <para>Runs a job that does complicated math</para>
        /// </summary>
        private Vector4[] DoComplicatedMath(DataType prerequisiteOne, DataType prerequisiteTwo, DataType prerequisiteThree, ref SystemState state)
        {
            // Create some natives
            NativeList<Vector4> nativeMath = new NativeList<Vector4>(Allocator.TempJob);
            // Do the actual math
            new ComplicatedMathJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, nativeMath).Run();
            // Convert the natives
            Vector4[] result = UtilitiesNotClassButStructIguess.ConvertFromNative(nativeMath);
            // Dispose the natives
            nativeMath.Dispose();
            // Return the result
            return result;
        }

Enabling me to do things like
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().

NativeList<DataType> result = new NativeList<DataType>(Allocator.TempJob)

new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();

DoThingsWithResult(result);

prerequisiteOne = ChangePrerequisiteOne(prerequisiteOne);
prerequisiteTwo = ChangePrerequisiteTwo(prerequisiteTwo);
prerequisiteThree = ChangePrerequisiteThree(prerequisiteThree);

new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();

DoThingsWithResult(result);

prerequisiteOne = ChangePrerequisiteOne(prerequisiteOne);
prerequisiteTwo = ChangePrerequisiteTwo(prerequisiteTwo);
prerequisiteThree = ChangePrerequisiteThree(prerequisiteThree);

new SomeJob(prerequisiteOne, prerequisiteTwo, prerequisiteThree, result).Run();

DoThingsWithResult(result);

result.Dispose();

For which calling the job is like calling a function.

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.

Some good advises, thx

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

1 Like