Particle System C# Job System support

Please use this thread to provide feedback on our C# Job System support.

Grab the build from Particle System C# Job System support

1 Like

I missed the revelation of Unity C# Job System back in mid-2017, so I am kinda confused and I think many other effect artists here like me who began learning C# (and HLSL) primarily for expanding the variety of Shuriken-based effects would have a funny time to comprehend why this Job System is introduced in the first place. The manual says nothing. There is an hour-long official video about this topic with no timestamp for us to look for vital info. How to use it in the right way or the possibilities it opens are nowhere to be found.

Amateurishly I can see the benefits of getting greater control over manually assigning jobs for multi-threaded CPU, but the best-practice part is often left uninstructed by Unity, resulting the effect artists taking ages to figure out the programming and worst being blamed by programmers for coding not in the fashion/mindset of a true programmer when there is no dedicated technical artist around. So I just want Unity to give us in-depth instructions (which is currently none) of how it works and what pitfall to avoid when dealing with such programmatic feature by effect artist.

Try: https://www.youtube.com/results?search_query=unity+job+system

Thanks. I just realized the full introduction of job system exists unexpectedly on GitHub instead of Unity.com

Can a brother get a link up in here?

https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/content/job_system.md

I still cannot comprehend the point of hosting crucial info off-site. Perhaps I should get used to it soon.

I will have to try this out. I did make a job implementation using 2018.1 that changed the color of particles based on their direction. With this experimental job for particle systems, I should be able to make a more efficient version of the job.

1 Like

Here is what I have for it so far. It seems to work.

using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.ParticleSystemJobs;
public class ParticleColorByDirection : MonoBehaviour
{
    public ParticleSystem ps;
    public List<Vector3> directions = new List<Vector3>();
    public Color [] colors;
    NativeArray<Color> nativeColor;
    NativeArray<Vector3> dir;
    public bool moveTowardsNewColor;
    public bool useDeltaTimeLerp;
    public float lerpValue= 0.5f;
    void Update ()
    {
        var job = new ParticleDirectionColor();
        dir = new NativeArray<Vector3>(directions.ToArray(), Allocator.Temp);

        nativeColor = new NativeArray<Color>(colors, Allocator.Temp);
        job.colors = nativeColor;
        job.colorDirections = dir;
        if (useDeltaTimeLerp)
            job.delta = Time.deltaTime;
        else
            job.delta = lerpValue;
        job.moveTowards = moveTowardsNewColor;
        ps.SetJob(job);
       
    }
    private void LateUpdate()
    {
        nativeColor.Dispose();
        dir.Dispose();
    }
    struct ParticleDirectionColor : IParticleSystemJob
     {
        [ReadOnly]
        public NativeArray<Color> colors;
        [ReadOnly]
        public NativeArray<Vector3> colorDirections;
        [ReadOnly]
        public float delta;
        [ReadOnly]
        public bool moveTowards;
        public void ProcessParticleSystem(JobData particles)
        {
            var startColors = particles.startColors;
            
            int closestDirectionIndex = -1;
            float closestDist = float.MaxValue;
            Vector3 particleDirection;
            var startCo = particles.startColors;
            var velocities = particles.velocities;
            for (int i = 0; i < particles.count; i++)
            {
               
                particleDirection = new Vector3(velocities.x[i], velocities.y[i], velocities.z[i]).normalized;
                for (int x = 0; x < colorDirections.Length; x++)
                {
                    float dist = Vector3.Distance(particleDirection, colorDirections [x].normalized);
                    float ang = Vector3.Angle(particleDirection, colorDirections [x].normalized);
                    float weight = 1f - (ang / 180f);
                    if (closestDist > dist)
                    {
                        closestDist = dist;
                        closestDirectionIndex = x;
                    }
                }
                if (moveTowards)
                    startColors [i] = Color.Lerp(startColors [i], colors [closestDirectionIndex], delta);
                else
                    startColors [i] = colors [closestDirectionIndex];
            }
        }
     }
    
}
1 Like

It appears to work fine, but from what I can see it simply offloads the Job into one of the worker threads.

If you’re already using the Job System processing an IJobParallelFor, this results in a performance decrease because your IParticleSystemJob gets run on a random worker after the IJobParallelFor.

The timeline below shows a parallel job running a 2-octave Perlin noise on a position, and then feeding that to a particle system job. There’s 90,000 particles in this instance.

3554564--286049--Screen Shot 2018-07-05 at 17.04.59.png

1 Like

I guess it would be better if the particle job was a for-each too, and you could choose the granularity? Instead of one big job.

Yep, that would be super helpful. Most of the time, if you’re directly manipulating particles it’s because there’s a lot of them, so being able to split the for loop across multiple workers would give a huge boost.

Out of curiosity, how would you dependency chain jobs together? In my case I want to parallel-for a bunch of work, then throw the result over to particle system job. The SetJob method doesn’t seem to support JobHandle dependsOn, and theres no job.Complete().

What is the status of this? I’d be very interested in manipulating particle positions in an IJobParallelFor.

We didn’t get it finished in time for 2018.3, so we are looking at a 2019.1 release now.

1 Like

I am really looking forward for the new release. Is this feature included in 2019.1?

At the moment I am calculating positions for particles in a job and then setting the particle positions afterwards in a normal loop.

It already gives a lot of performance running the position calculations with burst and in parallel, but when I can modify the positions directly it is going to be great! :slight_smile:

Further question: When ParticleSystem positions are updated internally?

Yes it is :slight_smile: Hope it works well for you, all feedback welcome!

I tried the 2019.1 alpha and it was a nice way to manipulate particle data. However, I was hoping to take advantage of parallel jobs with particles (this was mentioned as upcoming feature in Unity Berlin particle video by Karl Jones) and it does not seem to be possible at the moment. Any word when this could be coming?

The way to use the ParticleSystemJob was also unconventional from normal jobs. F.e. it is not possible to set dependencies to other jobs, but I guess that’s because of how the particles are updated internally.

Also I am interested to hear any word on user made particle system modules :slight_smile:

In live Unity 2018.3 project I manipulate particle positions by having a local particle array and using SetParticles method. I am not using GetParticles as I want to be in control of the particle array order (it is unsure where Unity engine internally moves the newly emitted or dead particles). Texture sheet animation module works when decreasing deltatime from particles remainingLifeTime. However rotation over lifetime module does not work. Is there a way to get it to work in this scenario? One option might be querying the rotation per particle using the modules MinMaxCurve.Evaluate(time, particleRandomSeed), but it feels like waste of performance.

Both of these points relate to the same limitation in how the job is scheduled currently. Right now, we simply call the c# job from the end of the internal particle update job. That is 1 job, hence the c# job is also 1 job. And because it’s internal, you don’t get to see the JobHandle, so no dependencies/waiting for completion.

What I’m hoping to do, is change the API to work something like this:

  • You declare a MonoBehaviour function, similar to how you declare OnParticleCollision today. That function is called something like OnParticleJobScheduled. It gives you the JobHandle of the internal job. You are then free to schedule whatever you like from that handle: eg a foreach job with a granularity of your choosing, or just 1 job like how it works today.
  • Allowing you to do the scheduling will also give you a JobHandle back, to create your own dependency chains, like you can do with all other c# jobs.

Karl believes that we could provide a custom module UI in the Inspector as new module(s), with each entry backed by a C# job, to let you build custom modules. I believe that you can already build custom modules using C# jobs, and as a programmer needs to write the job/module, that same programmer might as well just add the UI in the MonoBehaviour script too. So I’m not really sure there is much for us to add to make this possible (except i want to be able to attach multiple c# jobs to a particle update, whereas right now it’s just 1.

When we make the improvements to the API, I may try writing some example “custom modules” to prove/disprove the concept.

New particles are emitted at the end, and when a particle dies, the live particle at the end of the array is copied into the slot of the dead particle, to maintain a contiguous array. It’s hard for you to track this in script, but it is possible via the Custom Data API: see the example about adding a unique ID to each particle: https://docs.unity3d.com/ScriptReference/ParticleSystem.SetCustomParticleData.html

Maybe one day we will have a proper event system…

I’m not sure, sorry :frowning:

1 Like

Is it possible to process collision events inside IParticleSystemJob?
E.g. static / dynamic collisions when particle collides with something.

I’d love to manipulate particles that collided with something. That would be great.

Hey, thanks for the feedback. No, it’s not possible right now, but it is a great idea. I’ll add it to our roadmap for this feature as we try to get it out of experimental status.

4 Likes

Are their any details on what was released with 2019.1? The Release Notes only have a one liner.

Also, are there any samples or doco?